Skip to content

Commit 2168f7e

Browse files
committed
update test case
1 parent c31e0ef commit 2168f7e

2 files changed

Lines changed: 24 additions & 22 deletions

File tree

Lib/test/test_free_threading/test_dict.py

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -358,8 +358,8 @@ def writer():
358358

359359
def test_racing_dict_update_and_method_lookup_with_inline_values(self):
360360
# gh-149816: sub-case 108
361-
# The race below checks that setattr() does not write into a detached
362-
# dict after __dict__ is replaced by another thread.
361+
# The race below checks that a detached dict is still valid
362+
# when racing setattr with __dict__ replacement
363363
class Target:
364364
pass
365365

@@ -371,44 +371,46 @@ def appender(obj: Target, start: Barrier, iter_times: int) -> None:
371371
index += 1
372372
time.sleep(0)
373373

374-
def replacer(obj: Target, start: Barrier, iter_times: int,
375-
detached_dict_queue: list[tuple[dict, frozenset]]) -> None:
374+
def replacer(obj: Target, start: Barrier, iter_times: int, churn_size: int) -> None:
376375
start.wait()
377376
for _ in range(iter_times):
378-
dict_snapshot = obj.__dict__
377+
old_dict = obj.__dict__
379378
obj.__dict__ = {}
380-
current_keys = frozenset(dict_snapshot)
381-
detached_dict_queue.append((dict_snapshot, current_keys))
379+
del old_dict
382380
time.sleep(0)
383-
384-
def race(iter_times: int, appender_threads: int,
381+
# create a list of dicts to trigger a realloc of the dict's table
382+
# and ensure that the old dict is not used after it is deleted
383+
realloc_trigger_list = [{"k": j} for j in range(churn_size)]
384+
del realloc_trigger_list
385+
386+
def race(iter_times: int,
387+
churn_size: int,
388+
appender_threads: int,
385389
replacer_threads: int) -> None:
386390
obj = Target()
387391
setattr(obj, "origin", 0)
388392
_ = obj.__dict__ # Access __dict__ to ensure it's initialized
389393

390394
start = Barrier(appender_threads + replacer_threads)
391-
detached_dict_queue: list[tuple[dict, frozenset]] = []
392395
threads = []
393396
for _ in range(appender_threads):
394397
threads.append(Thread(target=appender, args=(obj, start, iter_times),
395398
name="appender"))
396399
for _ in range(replacer_threads):
397-
threads.append(Thread(target=replacer, args=(obj, start, iter_times, detached_dict_queue),
400+
threads.append(Thread(target=replacer, args=(obj, start, iter_times, churn_size),
398401
name="replacer"))
399402

400-
with threading_helper.start_threads(threads):
401-
pass
402-
403-
for dict_snapshot, current_keys in detached_dict_queue:
404-
self.assertEqual(set(dict_snapshot), current_keys,
405-
f"Detached dict keys {set(dict_snapshot)} " +
406-
f"do not match current keys {current_keys}")
403+
with threading_helper.catch_threading_exception() as cm:
404+
with threading_helper.start_threads(threads):
405+
pass
406+
if cm.exc_type is not None:
407+
raise cm.exc_value
407408

408-
ITER_TIMES = 50_000
409+
ITER_TIMES = 2_000
409410
APPENDER_THREADS = 8
410411
REPLACER_THREADS = 8
411-
race(ITER_TIMES, APPENDER_THREADS, REPLACER_THREADS)
412+
CHURN_SIZE = 512
413+
race(ITER_TIMES, CHURN_SIZE, APPENDER_THREADS, REPLACER_THREADS)
412414

413415
if __name__ == "__main__":
414416
unittest.main()
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
Fix a race in free-threaded builds where storing an instance attribute could
2-
write to a detached ``__dict__`` if another thread replaced ``obj.__dict__``
3-
concurrently.
2+
use a stale borrowed reference to an instance ``__dict__`` after another thread
3+
replaced it.

0 commit comments

Comments
 (0)