Bug report
Bug description:
Decimal.__hash__() writes its lazy hash cache (self->hash) with plain stores (-1 default). On a free-threaded build, two threads calling hash() on the same shared Decimal race on the cache field - one reads the -1 sentinel while another stores the computed hash. hash() looks read-only to callers, so a shared Decimal is not safe to hash concurrently.
This is a sub-issue of #153852, see gist here.
Reproducer:
import sys, threading
from decimal import Decimal
assert not sys._is_gil_enabled(), "run free-threaded: PYTHON_GIL=0"
NT = 4
ROUNDS = 4000
pool = [None]
enter = threading.Barrier(NT + 1)
leave = threading.Barrier(NT + 1)
def worker():
for _ in range(ROUNDS):
enter.wait()
for d in pool[0]:
hash(d) # dec_hash: read self->hash==-1, then write it
leave.wait()
ts = [threading.Thread(target=worker) for _ in range(NT)]
for t in ts: t.start()
for r in range(ROUNDS):
pool[0] = [Decimal(f"{r}.{i}") for i in range(64)] # fresh, unhashed each round
enter.wait(); leave.wait()
for t in ts: t.join()
print("done, no crash")
CPython versions tested on:
CPython main branch
Operating systems tested on:
No response
Linked PRs
Bug report
Bug description:
Decimal.__hash__()writes its lazy hash cache (self->hash) with plain stores (-1 default). On a free-threaded build, two threads calling hash() on the same shared Decimal race on the cache field - one reads the -1 sentinel while another stores the computed hash. hash() looks read-only to callers, so a shared Decimal is not safe to hash concurrently.This is a sub-issue of #153852, see gist here.
Reproducer:
CPython versions tested on:
CPython main branch
Operating systems tested on:
No response
Linked PRs