8000 gh-120608: Make reversed iterator work with free-threading by eendebakpt · Pull Request #120971 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-120608: Make reversed iterator work with free-threading #120971

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account 8000 related emails.

Already on GitHub? Sign in to your account

Merged
merged 33 commits into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
57bc2dc
Make reversed iterator thread-safe
eendebakpt Jun 24, 2024
7127a85
Add test for free-threading
eendebakpt Jun 24, 2024
687a976
📜🤖 Added by blurb_it.
blurb-it[bot] Jun 24, 2024
a74a33c
Apply suggestions from code review
eendebakpt Jun 25, 2024
de1b3c6
make reversed_len thread safe
eendebakpt Jun 25, 2024
3bef3fd
update news entry, make reversed_reduce safe
eendebakpt Jun 25, 2024
220b414
update reversed_setstate for free-threading
eendebakpt Jun 26, 2024
28aa548
Update Objects/enumobject.c
eendebakpt Jun 26, 2024
2574051
Merge branch 'main' into reverse_ft_v2
eendebakpt Jun 26, 2024
e052ca4
simplify test
eendebakpt Jun 26, 2024
8a7876a
Update Misc/NEWS.d/next/Core and Builtins/2024-06-24-20-08-55.gh-issu…
eendebakpt Jul 8, 2024
081ba40
review comments: use for loop instead of comprehension
eendebakpt Jul 8, 2024
b2f2dd3
move test; skip free_after_iterating test on ft build
eendebakpt Jul 8, 2024
8c6d136
Merge branch 'main' into reverse_ft_v2
eendebakpt Jul 8, 2024
125645e
Merge branch 'main' into reverse_ft_v2
eendebakpt Aug 7, 2024
b90add3
Merge branch 'main' into reverse_ft_v2
eendebakpt Aug 23, 2024
98f663c
Merge branch 'main' into reverse_ft_v2
eendebakpt Oct 15, 2024
75a5fce
Merge branch 'main' into reverse_ft_v2
eendebakpt Nov 23, 2024
c7f6f92
lint
eendebakpt Nov 23, 2024
568b6d4
Merge branch 'main' into reverse_ft_v2
eendebakpt Dec 24, 2024
a5524a6
Merge branch 'main' into reverse_ft_v2
eendebakpt Jan 14, 2025
a0c316b
improve test
eendebakpt Jan 14, 2025
fc80d56
use Barrier in the test
eendebakpt Jan 15, 2025
4d2a7fc
Merge branch 'main' into reverse_ft_v2
eendebakpt Feb 10, 2025
b1071df
remove testing code
eendebakpt Feb 10, 2025
aa71804
review comments
eendebakpt Mar 11, 2025
76931aa
rework
eendebakpt Mar 11, 2025
2c5e7ef
add thread helper code
eendebakpt Mar 11, 2025
b36bd50
Update Lib/test/test_free_threading/test_reversed.py
kumaraditya303 Mar 12, 2025
01f31a7
use ctx manager
kumaraditya303 Mar 12, 2025
ff702ef
Merge branch 'main' into reverse_ft_v2
eendebakpt Mar 12, 2025
c4c1ea9
Remove trailing comma in .github/workflows/build.yml
ambv Mar 12, 2025
eb60c8f
Revert "Remove trailing comma in .github/workflows/build.yml"
ambv Mar 12, 2025
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
39 changes: 39 additions & 0 deletions Lib/test/test_free_threading/test_reversed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import unittest
from threading import Barrier, Thread
from test.support import threading_helper

threading_helper.requires_working_threading(module=True)

class TestReversed(unittest.TestCase):

@threading_helper.reap_threads
def test_reversed(self):
# Iterating over the iterator with multiple threads should not
# emit TSAN warnings
number_of_iterations = 10
number_of_threads = 10
size = 1_000

barrier = Barrier(number_of_threads)
def work(r):
barrier.wait()
while True:
try:
l = r.__length_hint__()
next(r)
except StopIteration:
break
assert 0 <= l <= size
x = tuple(range(size))

for _ in range(number_of_iterations):
r = reversed(x)
worker_threads = []
for _ in range(number_of_threads):
worker_threads.append(Thread(target=work, args=[r]))
with threading_helper.start_threads(worker_threads):
pass
barrier.reset()

if __name__ == "__main__":
unittest.main()
3 changes: 2 additions & 1 deletion Lib/test/test_str.py
Original file line number Diff line number Diff line change
Expand Up @@ -2605,7 +2605,8 @@ def test_compare(self):

def test_free_after_iterating(self):
support.check_free_after_iterating(self, iter, str)
support.check_free_after_iterating(self, reversed, str)
if not support.Py_GIL_DISABLED:
support.check_free_after_iterating(self, reversed, str)

def test_check_encoding_errors(self):
# bpo-37388: str(bytes) and str.decode() must check encoding and errors
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Adapt :func:`reversed` for use in the free-theading build.
The :func:`reversed` is still not thread-safe in the sense that concurrent
iterations may see the same object, but they will not corrupt the interpreter
state.
29 changes: 20 additions & 9 deletions Objects/enumobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -439,20 +439,22 @@ reversed_next(PyObject *op)
{
reversedobject *ro = _reversedobject_CAST(op);
PyObject *item;
Py_ssize_t index = ro->index;
Py_ssize_t index = FT_ATOMIC_LOAD_SSIZE_RELAXED(ro->index);

if (index >= 0) {
item = PySequence_GetItem(ro->seq, index);
if (item != NULL) {
ro->index--;
FT_ATOMIC_STORE_SSIZE_RELAXED(ro->index, index - 1);
return item;
}
if (PyErr_ExceptionMatches(PyExc_IndexError) ||
PyErr_ExceptionMatches(PyExc_StopIteration))
PyErr_Clear();
}
ro->index = -1;
FT_ATOMIC_STORE_SSIZE_RELAXED(ro->index, -1);
#ifndef Py_GIL_DISABLED
Py_CLEAR(ro->seq);
#endif
return NULL;
}

Expand All @@ -461,13 +463,15 @@ reversed_len(PyObject *op, PyObject *Py_UNUSED(ignored))
{
reversedobject *ro = _reversedobject_CAST(op);
Py_ssize_t position, seqsize;
Py_ssize_t index = FT_ATOMIC_LOAD_SSIZE_RELAXED(ro->index);

if (ro->seq == NULL)
if (index == -1)
return PyLong_FromLong(0);
assert(ro->seq != NULL);
seqsize = PySequence_Size(ro->seq);
if (seqsize == -1)
return NULL;
position = ro->index + 1;
position = index + 1;
return PyLong_FromSsize_t((seqsize < position) ? 0 : position);
}

Expand All @@ -477,10 +481,13 @@ static PyObject *
reversed_reduce(PyObject *op, PyObject *Py_UNUSED(ignored))
{
reversedobject *ro = _reversedobject_CAST(op);
if (ro->seq)
Py_ssize_t index = FT_ATOMIC_LOAD_SSIZE_RELAXED(ro->index);
if (index != -1) {
return Py_BuildValue("O(O)n", Py_TYPE(ro), ro->seq, ro->index);
else
}
else {
return Py_BuildValue("O(())", Py_TYPE(ro));
}
}

static PyObject *
Expand All @@ -490,15 +497,19 @@ reversed_setstate(PyObject *op, PyObject *state)
Py_ssize_t index = PyLong_AsSsize_t(state);
if (index == -1 && PyErr_Occurred())
return NULL;
if (ro->seq != 0) {
Py_ssize_t ro_index = FT_ATOMIC_LOAD_SSIZE_RELAXED(ro->index);
// if the iterator is exhausted we do not set the state
// this is for backwards compatibility reasons. in practice this situation
// will not occur, see gh-120971
if (ro_index != -1) {
Py_ssize_t n = PySequence_Size(ro->seq);
if (n < 0)
return NULL;
if (index < -1)
index = -1;
else if (index > n-1)
index = n-1;
ro->index = index;
FT_ATOMIC_STORE_SSIZE_RELAXED(ro->index, index);
}
Py_RETURN_NONE;
}
Expand Down
Loading
0