77#include "pycore_genobject.h" // _PyCoro_GetAwaitableIter()
88#include "pycore_iterobject.h" // _PyCallIter_NewEx()
99#include "pycore_object.h" // _PyObject_GC_TRACK()
10+ #include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_LOAD_SSIZE_RELAXED()
1011#include "pycore_pyerrors.h" // _PyErr_FormatFromCause()
1112#include "pycore_pystate.h" // _PyThreadState_GET()
1213
1314
1415typedef struct {
1516 PyObject_HEAD
16- Py_ssize_t it_index ;
17- PyObject * it_seq ; /* Set to NULL when iterator is exhausted */
17+ Py_ssize_t it_index ; /* -1 when iterator is exhausted */
18+ PyObject * it_seq ; /* Set to NULL when iterator is exhausted
19+ (in the default build) */
1820} seqiterobject ;
1921
2022PyObject *
@@ -61,26 +63,41 @@ iter_iternext(PyObject *iterator)
6163
6264 assert (PySeqIter_Check (iterator ));
6365 it = (seqiterobject * )iterator ;
66+ Py_ssize_t index = FT_ATOMIC_LOAD_SSIZE_RELAXED (it -> it_index );
67+ if (index < 0 )
68+ return NULL ;
6469 seq = it -> it_seq ;
70+ #ifndef Py_GIL_DISABLED
6571 if (seq == NULL )
6672 return NULL ;
67- if (it -> it_index == PY_SSIZE_T_MAX ) {
73+ #endif
74+ if (index == PY_SSIZE_T_MAX ) {
6875 PyErr_SetString (PyExc_OverflowError ,
6976 "iter index too large" );
7077 return NULL ;
7178 }
7279
73- result = PySequence_GetItem (seq , it -> it_index );
80+ result = PySequence_GetItem (seq , index );
7481 if (result != NULL ) {
75- it -> it_index ++ ;
82+ /* PySequence_GetItem() can exhaust the iterator re-entrantly.
83+ * Preserve the exhaustion sentinel if it is observed. Concurrent
84+ * exhaustion can still race with the store, but remains memory-safe
85+ * because the sequence stays alive. */
86+ if (FT_ATOMIC_LOAD_SSIZE_RELAXED (it -> it_index ) >= 0 ) {
87+ FT_ATOMIC_STORE_SSIZE_RELAXED (it -> it_index , index + 1 );
88+ }
7689 return result ;
7790 }
7891 if (PyErr_ExceptionMatches (PyExc_IndexError ) ||
7992 PyErr_ExceptionMatches (PyExc_StopIteration ))
8093 {
94+ /* Mark the iterator exhausted before anything that can run
95+ * arbitrary code. */
96+ FT_ATOMIC_STORE_SSIZE_RELAXED (it -> it_index , -1 );
97+ #ifndef Py_GIL_DISABLED
98+ Py_CLEAR (it -> it_seq );
99+ #endif
81100 PyErr_Clear ();
82- it -> it_seq = NULL ;
83- Py_DECREF (seq );
84101 }
85102 return NULL ;
86103}
@@ -91,7 +108,8 @@ iter_len(PyObject *op, PyObject *Py_UNUSED(ignored))
91108 seqiterobject * it = (seqiterobject * )op ;
92109 Py_ssize_t seqsize , len ;
93110
94- if (it -> it_seq ) {
111+ Py_ssize_t index = FT_ATOMIC_LOAD_SSIZE_RELAXED (it -> it_index );
112+ if (index >= 0 && it -> it_seq != NULL ) {
95113 if (_PyObject_HasLen (it -> it_seq )) {
96114 seqsize = PySequence_Size (it -> it_seq );
97115 if (seqsize == -1 )
@@ -100,7 +118,7 @@ iter_len(PyObject *op, PyObject *Py_UNUSED(ignored))
100118 else {
101119 Py_RETURN_NOTIMPLEMENTED ;
102120 }
103- len = seqsize - it -> it_index ;
121+ len = seqsize - index ;
104122 if (len >= 0 )
105123 return PyLong_FromSsize_t (len );
106124 }
@@ -119,8 +137,9 @@ iter_reduce(PyObject *op, PyObject *Py_UNUSED(ignored))
119137 * call must be before access of iterator pointers.
120138 * see issue #101765 */
121139
122- if (it -> it_seq != NULL )
123- return Py_BuildValue ("N(O)n" , iter , it -> it_seq , it -> it_index );
140+ Py_ssize_t index = FT_ATOMIC_LOAD_SSIZE_RELAXED (it -> it_index );
141+ if (index >= 0 && it -> it_seq != NULL )
142+ return Py_BuildValue ("N(O)n" , iter , it -> it_seq , index );
124143 else
125144 return Py_BuildValue ("N(())" , iter );
126145}
@@ -134,10 +153,10 @@ iter_setstate(PyObject *op, PyObject *state)
134153 Py_ssize_t index = PyLong_AsSsize_t (state );
135154 if (index == -1 && PyErr_Occurred ())
136155 return NULL ;
137- if (it -> it_seq != NULL ) {
138- if ( index < 0 )
139- index = 0 ;
140- it -> it_index = index ;
156+ if (index < 0 )
157+ index = 0 ;
158+ if ( it -> it_seq && FT_ATOMIC_LOAD_SSIZE_RELAXED ( it -> it_index ) > = 0 ) {
159+ FT_ATOMIC_STORE_SSIZE_RELAXED ( it -> it_index , index ) ;
141160 }
142161 Py_RETURN_NONE ;
143162}
0 commit comments