From 24771bcd4888de8dd5674a6b38fe7713d97bf1ed Mon Sep 17 00:00:00 2001 From: Yurii Karabas <1998uriyyo@gmail.com> Date: Sun, 3 Jan 2021 13:45:02 +0200 Subject: [PATCH 1/4] Fix issue when thread doesn't copy context of parent thread --- Lib/test/test_threading.py | 26 +++++++++++++++++++ Lib/threading.py | 9 +++++-- .../2021-01-03-13-44-44.bpo-42815.IJqHPr.rst | 2 ++ 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2021-01-03-13-44-44.bpo-42815.IJqHPr.rst diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 0a4372ec2df39f..88abdb98b8e0e9 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -8,6 +8,7 @@ from test.support.import_helper import import_module from test.support.script_helper import assert_python_ok, assert_python_failure +import contextvars import random import sys import _thread @@ -855,6 +856,31 @@ def __del__(self): """) self.assertEqual(out.rstrip(), b"thread_dict.atexit = 'value'") + def test_contextvars(self): + context_var = contextvars.ContextVar("context_var") + context_var.set("default") + + result = None + + def target(): + nonlocal result + result = context_var.get() + + t = threading.Thread(target=target) + t.start() + t.join() + + self.assertEqual(result, "default") + + custom_ctx = contextvars.Context() + custom_ctx.run(lambda: context_var.set("custom")) + + t = threading.Thread(target=target, context=custom_ctx) + t.start() + t.join() + + self.assertEqual(result, "custom") + class ThreadJoinOnShutdown(BaseTestCase): diff --git a/Lib/threading.py b/Lib/threading.py index 7b3d63dd211ea4..6f7d7d0b7bce01 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -8,6 +8,7 @@ from time import monotonic as _time from _weakrefset import WeakSet from itertools import islice as _islice, count as _count +from contextvars import copy_context as _copy_context try: from _collections import deque as _deque except ImportError: @@ -782,7 +783,7 @@ class Thread: _initialized = False def __init__(self, group=None, target=None, name=None, - args=(), kwargs=None, *, daemon=None): + args=(), kwargs=None, *, daemon=None, context=None): """This constructor should always be called with keyword arguments. Arguments are: *group* should be None; reserved for future extension when a ThreadGroup @@ -827,6 +828,10 @@ class is implemented. else: self._daemonic = current_thread().daemon self._ident = None + if context is not None: + self._context = context + else: + self._context = _copy_context() if _HAVE_THREAD_NATIVE_ID: self._native_id = None self._tstate_lock = None @@ -907,7 +912,7 @@ def run(self): """ try: if self._target: - self._target(*self._args, **self._kwargs) + self._context.run(self._target, *self._args, **self._kwargs) finally: # Avoid a refcycle if the thread is running a function with # an argument that has a member that points to the thread. diff --git a/Misc/NEWS.d/next/Library/2021-01-03-13-44-44.bpo-42815.IJqHPr.rst b/Misc/NEWS.d/next/Library/2021-01-03-13-44-44.bpo-42815.IJqHPr.rst new file mode 100644 index 00000000000000..804bdfb9820626 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-01-03-13-44-44.bpo-42815.IJqHPr.rst @@ -0,0 +1,2 @@ +Fix issue when new thread doesn't copy context of a parent thread. Patch +provided by Yurii Karabas. From 720a14465ea98c7e8f0b1b129ddac7f03f714753 Mon Sep 17 00:00:00 2001 From: Yurii Karabas <1998uriyyo@gmail.com> Date: Sun, 3 Jan 2021 16:27:44 +0200 Subject: [PATCH 2/4] Fix deciamal threading tests --- Lib/test/test_decimal.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index dbd58e8a6519b1..2c169a3887a048 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -32,6 +32,7 @@ import unittest import numbers import locale +import contextvars from test.support import (run_unittest, run_doctest, is_resource_enabled, requires_IEEE_754, requires_docstrings, requires_legacy_unicode_capi) @@ -1618,8 +1619,8 @@ def test_threading(self): self.finish1 = threading.Event() self.finish2 = threading.Event() - th1 = threading.Thread(target=thfunc1, args=(self,)) - th2 = threading.Thread(target=thfunc2, args=(self,)) + th1 = threading.Thread(target=thfunc1, args=(self,), context=contextvars.Context()) + th2 = threading.Thread(target=thfunc2, args=(self,), context=contextvars.Context()) th1.start() th2.start() From ab9eff1ba8b7c8381e4f0eeaa335e8994a6394bf Mon Sep 17 00:00:00 2001 From: Yurii Karabas <1998uriyyo@gmail.com> Date: Wed, 11 Aug 2021 12:01:42 +0300 Subject: [PATCH 3/4] Delete context to avoid refcycle --- Lib/threading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/threading.py b/Lib/threading.py index 8c70240e21576a..3d5a86c5cb042c 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -952,7 +952,7 @@ def run(self): finally: # Avoid a refcycle if the thread is running a function with # an argument that has a member that points to the thread. - del self._target, self._args, self._kwargs + del self._context, self._target, self._args, self._kwargs def _bootstrap(self): # Wrapper around the real bootstrap code that ignores From 1fa09025117ef3b31b184efef153097fd56f3c94 Mon Sep 17 00:00:00 2001 From: Yurii Karabas <1998uriyyo@gmail.com> Date: Wed, 11 Aug 2021 12:27:18 +0300 Subject: [PATCH 4/4] Uncomment _contextvars at Modules/Setup --- Modules/Setup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Setup b/Modules/Setup index 5e26472446677a..a48018dd6b0270 100644 --- a/Modules/Setup +++ b/Modules/Setup @@ -169,7 +169,7 @@ _symtable symtablemodule.c #array -DPy_BUILD_CORE_MODULE arraymodule.c # array objects #cmath cmathmodule.c _math.c -DPy_BUILD_CORE_MODULE # -lm # complex math library functions #math mathmodule.c _math.c -DPy_BUILD_CORE_MODULE # -lm # math library functions, e.g. sin() -#_contextvars _contextvarsmodule.c # Context Variables +_contextvars _contextvarsmodule.c # Context Variables #_struct -DPy_BUILD_CORE_MODULE _struct.c # binary structure packing/unpacking #_weakref _weakref.c # basic weak reference support #_testcapi _testcapimodule.c # Python C API test module