[ZODB-Dev] Same transaction object (re)-used for subsequent requests?

Andreas Jung lists at zopyx.com
Tue May 1 04:53:18 EDT 2007


Hi,

I encountered the following strange behavior with Zope 2.8.8.

The following code is used to integrate SQLAlchemy with
Zope. A registered utility subclassing ZopeBaseWrapper provides
a 'connection' property. This property should always return
for a given transaction the same sqlalchemy.Connection object
(which is like a connection from a connection pool within a DA).

Within a thread-local cache the last id of the transaction and the last 
connection is stored in order to return the connection from the cache if 
the property 'connection' is called/used multiple times within one 
request/one
transaction.

A ConnectionDataManger instance is added a data manager to the current
transaction in order to integrate the ZODB transaction with the transaction 
system by SQLAlchemy.


class ConnectionDataManager(object):
    """ Wraps connection into transaction context of Zope """

    implements(IDataManager)

    def __init__(self, connection):
        self.connection = connection
        self.transaction = connection.begin()

    def tpc_begin(self, trans):
        log('tpc_begin() - %s' % trans)
        pass

    def abort(self, trans):
        self.transaction.rollback()
        self.connection.close()
        self.connection = None
        log('abort() - %s' % trans)

    def commit(self, trans):
        self.transaction.commit()
        log('commit() - %s' % trans)
        self.connection.close()
        self.connection = None

    def tpc_vote(self, trans):
        pass

    def tpc_finish(self, trans):
        log('tcp_finish() - %s' % trans)
        pass

    def tpc_abort(self, trans):
        log('tcp_abort() - %s' % trans)
        pass

    def sortKey(self):
        return str(id(self))

_connection_cache = threading.local() # module-level cache

class ZopeBaseWrapper(BaseWrapper):

    @property
    def connection(self):

        if not hasattr(_connection_cache, 'last_connection'):
            _connection_cache.last_transaction = None
            _connection_cache.last_connection = None

        # get current transaction
        txn = transaction.get()
        txn_str = str(txn)
        log('current thread - %s' % threading.currentThread())
        log('checking for transaction - %s' % txn_str)

        # return cached connection if we are within the same transaction
        # and same thread
        if txn_str == _connection_cache.last_transaction:
            log('returning cached connection - %s' % 
_connection_cache.last_connection)
            return _connection_cache.last_connection

        # no cached connection, let's create a new one
        connection = self.engine.connect()
        log('creating new connection - %s' % connection)

        # register a DataManager with the current transaction
        txn.join(ConnectionDataManager(connection))

        # update thread-local cache
        _connection_cache.last_transaction = txn_str
        _connection_cache.last_connection = connection

        # return the connection
        return connection

This works almost. However when I hammer my Zope instance using ab2 
(without concurrent request, option -c 1) then in some rare cases I see 
that a new request uses a formerly used transaction object. Look at the 
output

Request #1:
*** <_DummyThread(Dummy-1, started daemon)> - current thread - 
<_DummyThread(Dummy-1, started daemon)>
*** <_DummyThread(Dummy-1, started daemon)> - checking for transaction - 
<transaction._transaction.Transaction object at 0x2ba7b29e7050>
*** <_DummyThread(Dummy-1, started daemon)> - creating new connection - 
<sqlalchemy.engine.base.Connection object at 0x2ba7b29c4b90>
*** <_DummyThread(Dummy-1, started daemon)> - tpc_begin() - 
<transaction._transaction.Transaction object at 0x2ba7b29e7050>
*** <_DummyThread(Dummy-1, started daemon)> - commit() - 
<transaction._transaction.Transaction object at 0x2ba7b29e7050>
*** <_DummyThread(Dummy-1, started daemon)> - tcp_finish() - 
<transaction._transaction.Transaction object at 0x2ba7b29e7050>

Request #2:
*** <_DummyThread(Dummy-1, started daemon)> - current thread - 
<_DummyThread(Dummy-1, started daemon)>
*** <_DummyThread(Dummy-1, started daemon)> - checking for transaction - 
<transaction._transaction.Transaction object at 0x2ba7b29e7050>
*** <_DummyThread(Dummy-1, started daemon)> - returning cached connection - 
<sqlalchemy.engine.base.Connection object at 0x2ba7b29c4b90>

As you can see request #1 commits without a problem. But for the second 
request handled by the same thread the same transaction object is re-used.

Bug or feature?

Andreas

-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 186 bytes
Desc: not available
Url : http://mail.zope.org/pipermail/zodb-dev/attachments/20070501/406dc03a/attachment.bin


More information about the ZODB-Dev mailing list