Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace treading.local with contextvar #107

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 134 additions & 49 deletions src/transaction/_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
are associated with the right transaction.
"""
import sys
import threading
import itertools

from zope.interface import implementer
Expand All @@ -31,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
Expand Down Expand Up @@ -220,74 +226,153 @@ def run(self, func=None, tries=3):
raise


@implementer(ITransactionManager)
class ThreadTransactionManager(threading.local):
"""Thread-local
`transaction manager <transaction.interfaces.ITransactionManager>`.
if USE_CONTEXTVAR: # pragma: PY3
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you switch the if order it might make the diff much more easy to read:

if not USE_CONTEXTVAR:
   # old/current code that on the diff is only shown as being indented
else:
  # new code, that shows up as new code

otherwise a quick glimse at the code feels like the new code is actually the old one 😅

# Isolated transaction manager context state
transaction_manager_state = ContextVar("transaction_manager_state")

A thread-local transaction manager can be used as a global
variable, but has a separate copy for each thread.
@implementer(ITransactionManager)
class ThreadTransactionManager:
"""ContextVar
`transaction manager <transaction.interfaces.ITransactionManager>`.

Advanced applications can use the `manager` attribute to get a
wrapped `TransactionManager` to allow cross-thread calls for
graceful shutdown of data managers.
"""
A ContextVar transaction manager can be used as a global
variable, but has a separate copy for each context and thread.

def __init__(self):
self.manager = TransactionManager()
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
@property
def explicit(self):
return self.manager.explicit

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

else: # pragma: PY2

@implementer(ITransactionManager)
class ThreadTransactionManager(threading.local):
"""Thread-local
`transaction manager <transaction.interfaces.ITransactionManager>`.

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):
Expand Down