diff options
author | Armin Ronacher <armin.ronacher@active-4.com> | 2016-05-03 23:12:51 +0200 |
---|---|---|
committer | Armin Ronacher <armin.ronacher@active-4.com> | 2016-05-03 23:12:51 +0200 |
commit | 18529f23558a10ca9fdb33be25d57a4305152783 (patch) | |
tree | 3e62ab72929f39edd4c99b524e204cef73016727 | |
parent | cf3a388395d18b44c25db3d3d5174b7e468633d4 (diff) | |
download | raven-18529f23558a10ca9fdb33be25d57a4305152783.tar.gz |
Added support for improved context thread binding
-rw-r--r-- | CHANGES | 7 | ||||
-rw-r--r-- | raven/base.py | 14 | ||||
-rw-r--r-- | raven/context.py | 25 | ||||
-rw-r--r-- | tests/context/tests.py | 35 |
4 files changed, 74 insertions, 7 deletions
@@ -1,3 +1,10 @@ +Version 5.15.0 +-------------- + +* Improve thread binding for the context. This makes the main thread never + deactivate the client automatically on clear which means that more code + should automatically support breadcrumbs without changes. + Version 5.14.0 -------------- diff --git a/raven/base.py b/raven/base.py index 59388b2..0136c88 100644 --- a/raven/base.py +++ b/raven/base.py @@ -25,6 +25,11 @@ if sys.version_info >= (3, 2): else: import contextlib2 as contextlib +try: + from thread import get_ident as get_thread_ident +except ImportError: + from _thread import get_ident as get_thread_ident + import raven from raven.conf import defaults from raven.conf.remote import RemoteConfig @@ -186,14 +191,13 @@ class Client(object): if Raven is None: Raven = self + # We want to remember the creating thread id here because this + # comes in useful for the context special handling + self.main_thread_id = get_thread_ident() + from raven.context import Context self._context = Context(self) - # We always activate the context for the calling thread. This - # means even in the absence of code that activates the context for - # threads otherwise. - self._context.activate() - if install_sys_hook: self.install_sys_hook() diff --git a/raven/context.py b/raven/context.py index cb2efbf..03309f1 100644 --- a/raven/context.py +++ b/raven/context.py @@ -13,6 +13,11 @@ from weakref import ref as weakref from raven._compat import iteritems +try: + from thread import get_ident as get_thread_ident +except ImportError: + from _thread import get_ident as get_thread_ident + _active_contexts = local() @@ -42,6 +47,11 @@ class Context(local, Mapping, Iterable): if client is not None: client = weakref(client) self._client = client + # Because the thread auto activates the thread local this also + # means that we auto activate this thing. Only if someone decides + # to deactivate manually later another call to activate is + # technically necessary. + self.activate() self.data = {} self.exceptions_to_skip = set() self.breadcrumbs = raven.breadcrumbs.BreadcrumbBuffer() @@ -80,7 +90,9 @@ class Context(local, Mapping, Iterable): def __exit__(self, exc_type, exc_value, tb): self.deactivate() - def activate(self): + def activate(self, sticky=False): + if sticky: + self._sticky_thread = get_thread_ident() _active_contexts.__dict__.setdefault('contexts', set()).add(self) def deactivate(self): @@ -105,10 +117,19 @@ class Context(local, Mapping, Iterable): def get(self): return self.data - def clear(self, deactivate=True): + def clear(self, deactivate=None): self.data = {} self.exceptions_to_skip.clear() self.breadcrumbs.clear() + + # If the caller did not specify if it wants to deactivate the + # context for the thread we only deactivate it if we're not the + # thread that created the context (main thread). + if deactivate is None: + client = self.client + if client is not None: + deactivate = get_thread_ident() != client.main_thread_id + if deactivate: self.deactivate() diff --git a/tests/context/tests.py b/tests/context/tests.py index cc65564..9332655 100644 --- a/tests/context/tests.py +++ b/tests/context/tests.py @@ -1,5 +1,7 @@ +import threading from raven.utils.testutils import TestCase +from raven.base import Client from raven.context import Context @@ -35,3 +37,36 @@ class ContextTest(TestCase): 'biz': 'baz', } } + + def test_thread_binding(self): + client = Client() + called = [] + + class TestContext(Context): + + def activate(self): + Context.activate(self) + called.append('activate') + + def deactivate(self): + called.append('deactivate') + Context.deactivate(self) + + # The main thread activates the context but clear does not + # deactivate. + context = TestContext(client) + context.clear() + assert called == ['activate'] + + # But another thread does. + del called[:] + + def test_thread(): + # This activate is unnecessary as the first activate happens + # automatically + context.activate() + context.clear() + t = threading.Thread(target=test_thread) + t.start() + t.join() + assert called == ['activate', 'activate', 'deactivate'] |