From ed37b9e037ab32ca6917b811183e503b8e4ef093 Mon Sep 17 00:00:00 2001 From: Dokime Date: Mon, 5 Jul 2021 10:45:00 +0200 Subject: [PATCH 1/5] Replace treading.local with contextvar --- src/transaction/_manager.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/transaction/_manager.py b/src/transaction/_manager.py index 2be5de5..56ddc9f 100644 --- a/src/transaction/_manager.py +++ b/src/transaction/_manager.py @@ -19,6 +19,7 @@ import sys import threading import itertools +from contextvars import ContextVar from zope.interface import implementer @@ -58,6 +59,9 @@ def _new_transaction(txn, synchs): # so that Transactions "see" synchronizers that get registered after the # Transaction object is constructed. +# Isolated transaction manager context state +transaction_manager_state = ContextVar("transaction_manager_state") + @implementer(ITransactionManager) class TransactionManager(object): @@ -221,20 +225,24 @@ def run(self, func=None, tries=3): @implementer(ITransactionManager) -class ThreadTransactionManager(threading.local): - """Thread-local +class ThreadTransactionManager: + """ContextVar `transaction manager `. - A thread-local transaction manager can be used as a global - variable, but has a separate copy for each thread. + A ContextVar transaction manager can be used as a global + variable, but has a separate copy for each context and 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 manager(self): + try: + return transaction_manager_state.get() + except LookupError: + transaction_manager_state.set(TransactionManager()) + return transaction_manager_state.get() @property def explicit(self): From 3c3713aefbf0fb19b487ea3cd0b79785d77554f6 Mon Sep 17 00:00:00 2001 From: Dokime Date: Mon, 5 Jul 2021 17:04:26 +0200 Subject: [PATCH 2/5] Fix backward compatibility --- src/transaction/_manager.py | 197 +++++++++++++++++++++++++----------- 1 file changed, 137 insertions(+), 60 deletions(-) diff --git a/src/transaction/_manager.py b/src/transaction/_manager.py index 56ddc9f..609f8eb 100644 --- a/src/transaction/_manager.py +++ b/src/transaction/_manager.py @@ -17,9 +17,7 @@ are associated with the right transaction. """ import sys -import threading import itertools -from contextvars import ContextVar from zope.interface import implementer @@ -32,6 +30,13 @@ from transaction._compat import text_ from transaction._transaction import Transaction +USE_CONTEXTVAR = False +try: # Try to use ContextVar but fallback to threading.local + from contextvars import ContextVar + USE_CONTEXTVAR = True +except ImportError: + import threading + # We have to remember sets of synch objects, especially Connections. # But we don't want mere registration with a transaction manager to @@ -59,9 +64,6 @@ def _new_transaction(txn, synchs): # so that Transactions "see" synchronizers that get registered after the # Transaction object is constructed. -# Isolated transaction manager context state -transaction_manager_state = ContextVar("transaction_manager_state") - @implementer(ITransactionManager) class TransactionManager(object): @@ -224,78 +226,153 @@ def run(self, func=None, tries=3): raise -@implementer(ITransactionManager) -class ThreadTransactionManager: - """ContextVar - `transaction manager `. +if USE_CONTEXTVAR: + # Isolated transaction manager context state + transaction_manager_state = ContextVar("transaction_manager_state") - A ContextVar transaction manager can be used as a global - variable, but has a separate copy for each context and thread. + @implementer(ITransactionManager) + class ThreadTransactionManager: + """ContextVar + `transaction manager `. - Advanced applications can use the `manager` attribute to get a - wrapped `TransactionManager` to allow cross-thread calls for - graceful shutdown of data managers. - """ - @property - def manager(self): - try: - return transaction_manager_state.get() - except LookupError: - transaction_manager_state.set(TransactionManager()) - return transaction_manager_state.get() - - @property - def explicit(self): - return self.manager.explicit - - @explicit.setter - def explicit(self, v): - self.manager.explicit = v + A ContextVar transaction manager can be used as a global + variable, but has a separate copy for each context and thread. - def begin(self): - return self.manager.begin() + Advanced applications can use the `manager` attribute to get a + wrapped `TransactionManager` to allow cross-thread calls for + graceful shutdown of data managers. + """ + @property + def manager(self): + try: + return transaction_manager_state.get() + except LookupError: + transaction_manager_state.set(TransactionManager()) + return transaction_manager_state.get() - def get(self): - return self.manager.get() + @property + def explicit(self): + return self.manager.explicit - def __enter__(self): - return self.manager.__enter__() + @explicit.setter + def explicit(self, v): + self.manager.explicit = v - def commit(self): - return self.manager.commit() + def begin(self): + return self.manager.begin() - def abort(self): - return self.manager.abort() + def get(self): + return self.manager.get() - def __exit__(self, t, v, tb): - return self.manager.__exit__(t, v, tb) + def __enter__(self): + return self.manager.__enter__() - def doom(self): - return self.manager.doom() + def commit(self): + return self.manager.commit() - def isDoomed(self): - return self.manager.isDoomed() + def abort(self): + return self.manager.abort() - def savepoint(self, optimistic=False): - return self.manager.savepoint(optimistic) + def __exit__(self, t, v, tb): + return self.manager.__exit__(t, v, tb) - def registerSynch(self, synch): - return self.manager.registerSynch(synch) + def doom(self): + return self.manager.doom() - def unregisterSynch(self, synch): - return self.manager.unregisterSynch(synch) + def isDoomed(self): + return self.manager.isDoomed() - def clearSynchs(self): - return self.manager.clearSynchs() + def savepoint(self, optimistic=False): + return self.manager.savepoint(optimistic) - def registeredSynchs(self): - return self.manager.registeredSynchs() + def registerSynch(self, synch): + return self.manager.registerSynch(synch) - def attempts(self, number=3): - return self.manager.attempts(number) + def unregisterSynch(self, synch): + return self.manager.unregisterSynch(synch) - def run(self, func=None, tries=3): - return self.manager.run(func, tries) + 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) + +else: + + @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() + + def __enter__(self): + return self.manager.__enter__() + + 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): From a5f83a75c0b2e25a8d28dc0f09125a760c4df81f Mon Sep 17 00:00:00 2001 From: Dokime Date: Tue, 6 Jul 2021 11:25:47 +0200 Subject: [PATCH 3/5] Add no cover on env depends classes --- src/transaction/_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/transaction/_manager.py b/src/transaction/_manager.py index 609f8eb..1ccdb75 100644 --- a/src/transaction/_manager.py +++ b/src/transaction/_manager.py @@ -226,7 +226,7 @@ def run(self, func=None, tries=3): raise -if USE_CONTEXTVAR: +if USE_CONTEXTVAR: # pragma: no cover # Isolated transaction manager context state transaction_manager_state = ContextVar("transaction_manager_state") @@ -303,7 +303,7 @@ def attempts(self, number=3): def run(self, func=None, tries=3): return self.manager.run(func, tries) -else: +else: # pragma: no cover @implementer(ITransactionManager) class ThreadTransactionManager(threading.local): From e301b2a984889bcbf994c2f074b3d3a4ecf50e7a Mon Sep 17 00:00:00 2001 From: Jeremie Dokime Date: Tue, 6 Jul 2021 14:13:15 +0200 Subject: [PATCH 4/5] Update src/transaction/_manager.py Co-authored-by: Michael Howitz --- src/transaction/_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transaction/_manager.py b/src/transaction/_manager.py index 1ccdb75..eed6cc2 100644 --- a/src/transaction/_manager.py +++ b/src/transaction/_manager.py @@ -226,7 +226,7 @@ def run(self, func=None, tries=3): raise -if USE_CONTEXTVAR: # pragma: no cover +if USE_CONTEXTVAR: # pragma: PY3 # Isolated transaction manager context state transaction_manager_state = ContextVar("transaction_manager_state") From 65a35ca3ee70b61f953028113d79a05c686d98b8 Mon Sep 17 00:00:00 2001 From: Jeremie Dokime Date: Tue, 6 Jul 2021 14:13:25 +0200 Subject: [PATCH 5/5] Update src/transaction/_manager.py Co-authored-by: Michael Howitz --- src/transaction/_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transaction/_manager.py b/src/transaction/_manager.py index eed6cc2..30c5a4f 100644 --- a/src/transaction/_manager.py +++ b/src/transaction/_manager.py @@ -303,7 +303,7 @@ def attempts(self, number=3): def run(self, func=None, tries=3): return self.manager.run(func, tries) -else: # pragma: no cover +else: # pragma: PY2 @implementer(ITransactionManager) class ThreadTransactionManager(threading.local):