Apologies in advance if this suggestion is incorrect. I am seeing the _Transaction object shared between invocations in different requests, in different calls to the same method in the same request and between different threads making the same thread.
Doing the following appears to fix the problem for me. (DJango 1.3.1, Postgres 9.0.x, OSX and Ubuntu, Python 2.6)
You might want to drop the debugging.
'''python
from functools import wraps
from django.db import transaction, DEFAULT_DB_ALIAS, connections
import psycopg2.extensions
from django.http import HttpResponse
import logging
class _Transaction(object):
def init(self, using):
self.using = using
self.sid = None
self.complete = False
self.outer = True
def __enter__(self):
if transaction.is_managed(self.using):
if connections[self.using].features.uses_savepoints:
# We're already in a transaction; create a savepoint.
logging.error("-ESP-------BEGIN---------------- %s " % self)
self.sid = transaction.savepoint(self.using)
self.outer = False
else:
logging.error("-EE-------BEGIN---------------- %s " % self)
transaction.enter_transaction_management(using=self.using)
transaction.managed(True, using=self.using)
def __exit__(self, exc_type, exc_value, traceback):
if self.complete:
return False
if exc_value is None:
# commit operation
if self.outer and self.sid is None:
# Outer transaction
try:
logging.error("-EE-------COMMIT---------------- %s " % self)
transaction.commit(self.using)
except:
logging.error("-EE-------ROLLBACK-------------- %s " % self)
transaction.rollback(self.using)
raise
finally:
self._leave_transaction_management()
elif self.sid is not None:
# Inner savepoint
try:
logging.error("-ESP-------COMMIT---------------- %s " % self)
transaction.savepoint_commit(self.sid, self.using)
except:
logging.error("-ESP-------ROLLBACK-------------- %s " % self)
transaction.savepoint_rollback(self.sid, self.using)
raise
else:
# rollback operation
if self.outer and self.sid is None:
# Outer transaction
logging.error("-E--------ROLLBACK---------------- %s " % self)
transaction.rollback(self.using)
self._leave_transaction_management()
elif self.sid is not None:
# Inner savepoint
logging.error("-E--------ROLLBACK-SAVE-POINT----- %s " % self)
transaction.savepoint_rollback(self.sid, self.using)
return False
def _leave_transaction_management(self):
transaction.managed(False, using=self.using)
transaction.leave_transaction_management(using=self.using)
if not connections[self.using].is_managed() and connections[self.using].features.uses_autocommit:
connections[self.using]._set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
# Patch for bug in Django's psycopg2 backend; see:
# https://code.djangoproject.com/ticket/16047
class _TransactionWrapper():
def __init__(self, using):
self.using = using
def __call__(self, func):
@wraps(func)
def inner(*args, **kwargs):
t = _Transaction(self.using)
with t:
o = func(*args, **kwargs)
if hasattr(o, "status_code"):
# If the response is a http response object and the status code indicates
# any sort of failure, roll back either the savepoint or the whole
# transaction
if o.status_code < 200 or o.status_code >= 400:
if t.outer and t.sid is None: # outer transaction
logging.error("-R--------ROLLBACK---------------- %s " % t)
transaction.rollback(t.using)
t._leave_transaction_management()
elif t.sid is not None:
logging.error("-R--------ROLLBACK-SAVE-POINT----- %s " % t)
transaction.savepoint_rollback(t.sid, t.using)
t.complete = True
return o
return inner
def xact(using=None):
if using is None:
using = DEFAULT_DB_ALIAS
if callable(using):
return _TransactionWrapper(DEFAULT_DB_ALIAS)(using)
return _TransactionWrapper(using)
'''
Apologies in advance if this suggestion is incorrect. I am seeing the _Transaction object shared between invocations in different requests, in different calls to the same method in the same request and between different threads making the same thread.
Doing the following appears to fix the problem for me. (DJango 1.3.1, Postgres 9.0.x, OSX and Ubuntu, Python 2.6)
You might want to drop the debugging.
'''python
from functools import wraps
from django.db import transaction, DEFAULT_DB_ALIAS, connections
import psycopg2.extensions
from django.http import HttpResponse
import logging
class _Transaction(object):
def init(self, using):
self.using = using
self.sid = None
self.complete = False
self.outer = True
class _TransactionWrapper():
def xact(using=None):
if using is None:
using = DEFAULT_DB_ALIAS
if callable(using):
return _TransactionWrapper(DEFAULT_DB_ALIAS)(using)
return _TransactionWrapper(using)
'''