Skip to content

Modification to make annotation re-entrant and thread safe. #1

@ieb

Description

@ieb

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)

'''

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions