summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArmin Ronacher <armin.ronacher@active-4.com>2016-05-03 23:12:51 +0200
committerArmin Ronacher <armin.ronacher@active-4.com>2016-05-03 23:12:51 +0200
commit18529f23558a10ca9fdb33be25d57a4305152783 (patch)
tree3e62ab72929f39edd4c99b524e204cef73016727
parentcf3a388395d18b44c25db3d3d5174b7e468633d4 (diff)
downloadraven-18529f23558a10ca9fdb33be25d57a4305152783.tar.gz
Added support for improved context thread binding
-rw-r--r--CHANGES7
-rw-r--r--raven/base.py14
-rw-r--r--raven/context.py25
-rw-r--r--tests/context/tests.py35
4 files changed, 74 insertions, 7 deletions
diff --git a/CHANGES b/CHANGES
index 76843b4..5dcc0d3 100644
--- a/CHANGES
+++ b/CHANGES
@@ -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']