Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions Include/internal/pycore_cell.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define Py_INTERNAL_CELL_H

#include "pycore_critical_section.h"
#include "pycore_object.h"

#ifdef __cplusplus
extern "C" {
Expand Down Expand Up @@ -42,6 +43,31 @@ PyCell_GetRef(PyCellObject *cell)
return res;
}

static inline void
_PyCell_GetStackRef(PyCellObject *cell, _PyStackRef *value_addr)
{
PyObject *value;
#ifdef Py_GIL_DISABLED
value = _Py_atomic_load_ptr(&cell->ob_ref);
if (value != NULL) {
if (_Py_IsImmortal(value) || _PyObject_HasDeferredRefcount(value)) {
*value_addr = (_PyStackRef){ .bits = (uintptr_t)value | Py_TAG_DEFERRED };
return;
}
if (_Py_TryIncrefCompare(&cell->ob_ref, value)) {
*value_addr = _PyStackRef_FromPyObjectSteal(value);
return;
}
}
#endif
value = PyCell_GetRef(cell);
if (value == NULL) {
*value_addr = PyStackRef_NULL;
return;
}
*value_addr = PyStackRef_FromPyObjectSteal(value);
}

#ifdef __cplusplus
}
#endif
Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_opcode_metadata.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Include/internal/pycore_uop_metadata.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

67 changes: 30 additions & 37 deletions Lib/test/test_free_threading/test_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@
from threading import Thread
from unittest import TestCase

from test import support
from test.support import threading_helper


NTHREAD = 10
OBJECT_COUNT = 5_000


class C:
def __init__(self, v):
self.v = v


@threading_helper.requires_working_threading()
class TestList(TestCase):
@support.requires_resource('cpu')
def test_racing_iter_append(self):

l = []
OBJECT_COUNT = 10000

def writer_func():
for i in range(OBJECT_COUNT):
Expand All @@ -34,7 +34,7 @@ def reader_func():

writer = Thread(target=writer_func)
readers = []
for x in range(30):
for x in range(NTHREAD):
reader = Thread(target=reader_func)
readers.append(reader)
reader.start()
Expand All @@ -44,39 +44,32 @@ def reader_func():
for reader in readers:
reader.join()

@support.requires_resource('cpu')
def test_racing_iter_extend(self):
iters = [
lambda x: [x],
]
for iter_case in iters:
with self.subTest(iter=iter_case):
l = []
OBJECT_COUNT = 10000

def writer_func():
for i in range(OBJECT_COUNT):
l.extend(iter_case(C(i + OBJECT_COUNT)))

def reader_func():
while True:
count = len(l)
for i, x in enumerate(l):
self.assertEqual(x.v, i + OBJECT_COUNT)
if count == OBJECT_COUNT:
break

writer = Thread(target=writer_func)
readers = []
for x in range(30):
reader = Thread(target=reader_func)
readers.append(reader)
reader.start()

writer.start()
writer.join()
for reader in readers:
reader.join()
l = []

def writer_func():
for i in range(OBJECT_COUNT):
l.extend([C(i + OBJECT_COUNT)])

def reader_func():
while True:
count = len(l)
for i, x in enumerate(l):
self.assertEqual(x.v, i + OBJECT_COUNT)
if count == OBJECT_COUNT:
break

writer = Thread(target=writer_func)
readers = []
for x in range(NTHREAD):
reader = Thread(target=reader_func)
readers.append(reader)
reader.start()

writer.start()
writer.join()
for reader in readers:
reader.join()


if __name__ == "__main__":
Expand Down
14 changes: 7 additions & 7 deletions Lib/test/test_free_threading/test_monitoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@
import weakref

from sys import monitoring
from test import support
from test.support import threading_helper
from threading import Thread, _PyRLock
from unittest import TestCase


class InstrumentationMultiThreadedMixin:
thread_count = 10
func_count = 200
func_count = 50
fib = 12

def after_threads(self):
Expand All @@ -37,14 +36,13 @@ def work(self, n, funcs):
def start_work(self, n, funcs):
# With the GIL builds we need to make sure that the hooks have
# a chance to run as it's possible to run w/o releasing the GIL.
time.sleep(1)
time.sleep(0.1)
self.work(n, funcs)

def after_test(self):
"""Runs once after the test is done"""
pass

@support.requires_resource('cpu')
def test_instrumentation(self):
# Setup a bunch of functions which will need instrumentation...
funcs = []
Expand Down Expand Up @@ -220,29 +218,31 @@ def test_register_callback(self):
for ref in self.refs:
self.assertEqual(ref(), None)

@support.requires_resource('cpu')
def test_set_local_trace_opcodes(self):
def trace(frame, event, arg):
frame.f_trace_opcodes = True
return trace

loops = 1_000

sys.settrace(trace)
try:
l = _PyRLock()

def f():
for i in range(3000):
for i in range(loops):
with l:
pass

t = Thread(target=f)
t.start()
for i in range(3000):
for i in range(loops):
with l:
pass
t.join()
finally:
sys.settrace(None)


if __name__ == "__main__":
unittest.main()
6 changes: 3 additions & 3 deletions Lib/test/test_free_threading/test_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from threading import Thread
from unittest import TestCase

from test import support
from test.support import threading_helper


Expand Down Expand Up @@ -97,8 +96,9 @@ def reader_func():

self.run_one(writer_func, reader_func)

@support.requires_resource('cpu')
def test___class___modification(self):
loops = 200

class Foo:
pass

Expand All @@ -108,7 +108,7 @@ class Bar:
thing = Foo()
def work():
foo = thing
for _ in range(5000):
for _ in range(loops):
foo.__class__ = Bar
type(foo)
foo.__class__ = Foo
Expand Down
7 changes: 1 addition & 6 deletions Lib/test/test_super.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import threading
import unittest
from unittest.mock import patch
from test import support
from test.support import import_helper, threading_helper


Expand Down Expand Up @@ -515,10 +514,6 @@ def test___class___modification_multithreaded(self):
an audit hook.
"""

if support.Py_GIL_DISABLED:
# gh-124402: On a Free Threaded build, the test takes a few minutes
support.requires('cpu')

class Foo:
pass

Expand All @@ -528,7 +523,7 @@ class Bar:
thing = Foo()
def work():
foo = thing
for _ in range(5000):
for _ in range(200):
foo.__class__ = Bar
type(foo)
foo.__class__ = Foo
Expand Down
5 changes: 2 additions & 3 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -1649,12 +1649,11 @@ dummy_func(

inst(LOAD_DEREF, ( -- value)) {
PyCellObject *cell = (PyCellObject *)PyStackRef_AsPyObjectBorrow(GETLOCAL(oparg));
PyObject *value_o = PyCell_GetRef(cell);
if (value_o == NULL) {
_PyCell_GetStackRef(cell, &value);
if (PyStackRef_IsNull(value)) {
_PyEval_FormatExcUnbound(tstate, _PyFrame_GetCode(frame), oparg);
ERROR_IF(true, error);
}
value = PyStackRef_FromPyObjectSteal(value_o);
}

inst(STORE_DEREF, (v --)) {
Expand Down
5 changes: 2 additions & 3 deletions Python/executor_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions Python/generated_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Tools/tsan/suppressions_free_threading.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ race_top:tstate_is_freed
race_top:type_modified_unlocked
race_top:write_thread_id
race_top:PyThreadState_Clear
# see: https://github.com/python/cpython/issues/117721
race_top:lock_PyThread_release_lock
# Only seen on macOS, sample: https://gist.github.com/aisk/dda53f5d494a4556c35dde1fce03259c
race_top:set_default_allocator_unlocked

Expand Down
Loading