forked from CGM_Public/pretix_original
Do casual reads only on Galera
This commit is contained in:
@@ -9,7 +9,6 @@ from django.shortcuts import redirect, render
|
|||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from pretix.celery_app import app
|
from pretix.celery_app import app
|
||||||
from pretix.helpers.database import casual_reads
|
|
||||||
|
|
||||||
logger = logging.getLogger('pretix.base.async')
|
logger = logging.getLogger('pretix.base.async')
|
||||||
|
|
||||||
@@ -32,11 +31,10 @@ class AsyncAction:
|
|||||||
return JsonResponse(data)
|
return JsonResponse(data)
|
||||||
else:
|
else:
|
||||||
if res.ready():
|
if res.ready():
|
||||||
with casual_reads():
|
if res.successful() and not isinstance(res.info, Exception):
|
||||||
if res.successful() and not isinstance(res.info, Exception):
|
return self.success(res.info)
|
||||||
return self.success(res.info)
|
else:
|
||||||
else:
|
return self.error(res.info)
|
||||||
return self.error(res.info)
|
|
||||||
return redirect(self.get_check_url(res.id, False))
|
return redirect(self.get_check_url(res.id, False))
|
||||||
|
|
||||||
def get_success_url(self, value):
|
def get_success_url(self, value):
|
||||||
@@ -66,25 +64,24 @@ class AsyncAction:
|
|||||||
'ready': ready
|
'ready': ready
|
||||||
}
|
}
|
||||||
if ready:
|
if ready:
|
||||||
with casual_reads():
|
if res.successful() and not isinstance(res.info, Exception):
|
||||||
if res.successful() and not isinstance(res.info, Exception):
|
smes = self.get_success_message(res.info)
|
||||||
smes = self.get_success_message(res.info)
|
if smes:
|
||||||
if smes:
|
messages.success(self.request, smes)
|
||||||
messages.success(self.request, smes)
|
# TODO: Do not store message if the ajax client states that it will not redirect
|
||||||
# TODO: Do not store message if the ajax client states that it will not redirect
|
# but handle the mssage itself
|
||||||
# but handle the mssage itself
|
data.update({
|
||||||
data.update({
|
'redirect': self.get_success_url(res.info),
|
||||||
'redirect': self.get_success_url(res.info),
|
'message': str(self.get_success_message(res.info))
|
||||||
'message': str(self.get_success_message(res.info))
|
})
|
||||||
})
|
else:
|
||||||
else:
|
messages.error(self.request, self.get_error_message(res.info))
|
||||||
messages.error(self.request, self.get_error_message(res.info))
|
# TODO: Do not store message if the ajax client states that it will not redirect
|
||||||
# TODO: Do not store message if the ajax client states that it will not redirect
|
# but handle the mssage itself
|
||||||
# but handle the mssage itself
|
data.update({
|
||||||
data.update({
|
'redirect': self.get_error_url(),
|
||||||
'redirect': self.get_error_url(),
|
'message': str(self.get_error_message(res.info))
|
||||||
'message': str(self.get_error_message(res.info))
|
})
|
||||||
})
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_result(self, request):
|
def get_result(self, request):
|
||||||
@@ -93,11 +90,10 @@ class AsyncAction:
|
|||||||
return JsonResponse(self._return_ajax_result(res, timeout=0.25))
|
return JsonResponse(self._return_ajax_result(res, timeout=0.25))
|
||||||
else:
|
else:
|
||||||
if res.ready():
|
if res.ready():
|
||||||
with casual_reads():
|
if res.successful() and not isinstance(res.info, Exception):
|
||||||
if res.successful() and not isinstance(res.info, Exception):
|
return self.success(res.info)
|
||||||
return self.success(res.info)
|
else:
|
||||||
else:
|
return self.error(res.info)
|
||||||
return self.error(res.info)
|
|
||||||
return render(request, 'pretixpresale/waiting.html')
|
return render(request, 'pretixpresale/waiting.html')
|
||||||
|
|
||||||
def success(self, value):
|
def success(self, value):
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import contextlib
|
import contextlib
|
||||||
|
|
||||||
from django.conf import settings
|
from django.db import transaction
|
||||||
from django.db import connection, transaction
|
|
||||||
|
|
||||||
|
|
||||||
class DummyRollbackException(Exception):
|
class DummyRollbackException(Exception):
|
||||||
@@ -29,38 +28,9 @@ def rolledback_transaction():
|
|||||||
raise Exception('Invalid state, should have rolled back.')
|
raise Exception('Invalid state, should have rolled back.')
|
||||||
|
|
||||||
|
|
||||||
if 'mysql' in settings.DATABASES['default']['ENGINE'] and settings.DATABASE_IS_GALERA:
|
@contextlib.contextmanager
|
||||||
|
def casual_reads():
|
||||||
@contextlib.contextmanager
|
"""
|
||||||
def casual_reads():
|
Kept for backwards compatibility.
|
||||||
"""
|
"""
|
||||||
When pretix runs with a MySQL galera cluster as a database backend, we can run into the
|
yield
|
||||||
following problem:
|
|
||||||
|
|
||||||
* A celery thread starts a transaction, creates an object and commits the transaction.
|
|
||||||
It then returns the object ID into celery's result store (e.g. redis)
|
|
||||||
|
|
||||||
* A web thread pulls the object ID from the result store, but cannot access the object
|
|
||||||
yet as the transaction is not yet committed everywhere.
|
|
||||||
|
|
||||||
This sets the wsrep_sync_wait variable to deal with this problem.
|
|
||||||
|
|
||||||
See also:
|
|
||||||
|
|
||||||
* https://mariadb.com/kb/en/mariadb/galera-cluster-system-variables/#wsrep_sync_wait
|
|
||||||
|
|
||||||
* https://www.percona.com/doc/percona-xtradb-cluster/5.6/wsrep-system-index.html#wsrep_sync_wait
|
|
||||||
"""
|
|
||||||
with connection.cursor() as cursor:
|
|
||||||
cursor.execute("SET @wsrep_sync_wait_orig = @@wsrep_sync_wait;")
|
|
||||||
cursor.execute("SET SESSION wsrep_sync_wait = GREATEST(@wsrep_sync_wait_orig, 1);")
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
cursor.execute("SET SESSION wsrep_sync_wait = @wsrep_sync_wait_orig;")
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def casual_reads():
|
|
||||||
yield
|
|
||||||
|
|||||||
@@ -50,6 +50,14 @@ debug_fallback = "runserver" in sys.argv
|
|||||||
DEBUG = config.getboolean('django', 'debug', fallback=debug_fallback)
|
DEBUG = config.getboolean('django', 'debug', fallback=debug_fallback)
|
||||||
|
|
||||||
db_backend = config.get('database', 'backend', fallback='sqlite3')
|
db_backend = config.get('database', 'backend', fallback='sqlite3')
|
||||||
|
DATABASE_IS_GALERA = config.getboolean('database', 'galera', fallback=False)
|
||||||
|
if DATABASE_IS_GALERA and 'mysql' in db_backend:
|
||||||
|
db_options = {
|
||||||
|
'init_command': 'SET SESSION wsrep_sync_wait = 1;'
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
db_options = {}
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.' + db_backend,
|
'ENGINE': 'django.db.backends.' + db_backend,
|
||||||
@@ -58,10 +66,10 @@ DATABASES = {
|
|||||||
'PASSWORD': config.get('database', 'password', fallback=''),
|
'PASSWORD': config.get('database', 'password', fallback=''),
|
||||||
'HOST': config.get('database', 'host', fallback=''),
|
'HOST': config.get('database', 'host', fallback=''),
|
||||||
'PORT': config.get('database', 'port', fallback=''),
|
'PORT': config.get('database', 'port', fallback=''),
|
||||||
'CONN_MAX_AGE': 0 if db_backend == 'sqlite3' else 120
|
'CONN_MAX_AGE': 0 if db_backend == 'sqlite3' else 120,
|
||||||
|
'OPTIONS': db_options
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DATABASE_IS_GALERA = config.getboolean('database', 'galera', fallback=False)
|
|
||||||
|
|
||||||
STATIC_URL = config.get('urls', 'static', fallback='/static/')
|
STATIC_URL = config.get('urls', 'static', fallback='/static/')
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user