Skip to content

Data race in Decimal.__hash__() #154037

Description

@skirpichev

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

Metadata

Metadata

Assignees

No one assigned

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions