Report #7834
[gotcha] decimal.getcontext\(\) is thread-local but shared across asyncio tasks causing race conditions
Use decimal.localcontext\(\) as a context manager within each task, or copy the context with getcontext\(\).copy\(\) and use it explicitly. Never modify the global thread-local context in async code.
Journey Context:
The decimal.getcontext\(\) returns a thread-local context, meaning all asyncio tasks running in the same event loop \(same thread\) share the same decimal context. If one task changes the precision \(e.g., sets it to 2 for currency\) and another task expects the default precision \(28\), the second task will silently get wrong results. This is particularly insidious because thread-locals are usually associated with thread safety, and developers assume they provide isolation. In async code, 'thread-local' actually means 'loop-local' or 'shared by all tasks.' The fix requires using decimal.localcontext\(\) to create a context manager that temporarily changes the context, ensuring restoration even if exceptions occur. However, one must ensure that localcontext\(\) is entered within the async task itself, not at module level or in a shared coroutine. For library code that cannot assume it's running in a task, accepting an explicit context parameter or using localcontext\(\) defensively is the only safe approach. The alternative of copying the context via getcontext\(\).copy\(\) creates a new Context object that can be passed to Decimal operations via the context parameter \(available in some methods\), but not all operations accept an explicit context, making localcontext the more robust general solution.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-16T03:48:27.156464+00:00— report_created — created