Skip to content

Commit

Permalink
Changed the implementation of ThreadTransactionManager
Browse files Browse the repository at this point in the history
Changed the implementation of `ThreadTransactionManager` to be a
`thread.local` that wraps a `TransactionManager` rather than a
`thread.local` that inherits from `TransactionManager`.  It now
exposes a `manager` attribute that allows access to the wrapped
transaction manager to allow cross thread calls.  ZODB will use this
to call `unregisterSynch` when a connection is closed from a main
thread.
  • Loading branch information
jimfulton committed Oct 14, 2018
1 parent 0d29b05 commit 22a922e
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 5 deletions.
14 changes: 12 additions & 2 deletions docs/index.rst
Expand Up @@ -69,8 +69,9 @@ that it creates the transactions and keeps track of the current one. Whenever
an application wants to use the transaction machinery, it gets the current
transaction from the transaction manager before starting any operations

The default transaction manager for the transaction package is thread aware.
Each thread is associated with a unique transaction.
The default transaction manager, `transaction.manager`, is thread
local. You use it as a global variable, but every thread has it's own
copy. [#wrapped]_

Application developers will most likely never need to create their own
transaction managers.
Expand Down Expand Up @@ -141,3 +142,12 @@ Additional Documentation
integrations
api
developer


.. [#wrapped] The thread-local transaction manager,
`transaction.manager` wraps a regular transaction manager. You can
get the wrapped transaction manager using the `manager` attribute.
Implementers of data managers can use this **advanced** feature to
allow graceful shutdown from a central/main thread, by having their
`close` methods call `unregisterSynch` on the wrapped transaction
manager they obtained when created or opened.
78 changes: 75 additions & 3 deletions transaction/_manager.py
Expand Up @@ -215,12 +215,84 @@ def run(self, func=None, tries=3):
return result


class ThreadTransactionManager(TransactionManager, threading.local):
"""Thread-aware transaction manager.
_local = threading.local()
_lock = threading.Lock()
def _thread_local_manager():
with _lock:
try:
return _local.manager
except AttributeError:
_local.manager = TransactionManager()
return _local.manager

Each thread is associated with a unique transaction.

@implementer(ITransactionManager)
class ThreadTransactionManager(threading.local):
"""
Thread-local transaction manager.
A thread-local transaction manager can be used as a global
variable, but has a separate copy for each thread.
Advanced applications can use the `manager` attribute to get a
wrapped TransactionManager to allow cross-thread calls for
graceful shutdown of data managers.
"""

def __init__(self):
self.manager = TransactionManager()

@property
def explicit(self):
return self.manager.explicit

@explicit.setter
def explicit(self, v):
self.manager.explicit = v

def begin(self):
return self.manager.begin()

def get(self):
return self.manager.get()

__enter__ = get

def commit(self):
return self.manager.commit()

def abort(self):
return self.manager.abort()

def __exit__(self, t, v, tb):
return self.manager.__exit__(t, v, tb)

def doom(self):
return self.manager.doom()

def isDoomed(self):
return self.manager.isDoomed()

def savepoint(self, optimistic=False):
return self.manager.savepoint(optimistic)

def registerSynch(self, synch):
return self.manager.registerSynch(synch)

def unregisterSynch(self, synch):
return self.manager.unregisterSynch(synch)

def clearSynchs(self):
return self.manager.clearSynchs()

def registeredSynchs(self):
return self.manager.registeredSynchs()

def attempts(self, number=3):
return self.manager.attempts(number)

def run(self, func=None, tries=3):
return self.manager.run(func, tries)

class Attempt(object):

Expand Down
30 changes: 30 additions & 0 deletions transaction/tests/test__manager.py
Expand Up @@ -664,6 +664,36 @@ def test_notify_transaction_late_comers(self):
s.beforeCompletion.assert_called_with(t)
s.afterCompletion.assert_called_with(t)

def test_unregisterSynch_on_transaction_manager_from_serparate_thread(self):
# We should be able to get the underlying manager of the thread manager
# cand call methods from other threads.

import threading, transaction

started = threading.Event()
stopped = threading.Event()

synchronizer = self

class Runner(threading.Thread):

def __init__(self):
threading.Thread.__init__(self)
self.manager = transaction.manager.manager
self.setDaemon(True)
self.start()

def run(self):
self.manager.registerSynch(synchronizer)
started.set()
stopped.wait()

runner = Runner()
started.wait()
runner.manager.unregisterSynch(synchronizer)
stopped.set()
runner.join(1)

class AttemptTests(unittest.TestCase):

def _makeOne(self, manager):
Expand Down

0 comments on commit 22a922e

Please sign in to comment.