bpo-34410: Fix a crash in the tee iterator when re-enter it. (GH-15625)
RuntimeError is now raised in this case.
This commit is contained in:
parent
918b468b7d
commit
526a01467b
|
@ -645,6 +645,10 @@ loops that truncate the stream.
|
|||
used anywhere else; otherwise, the *iterable* could get advanced without
|
||||
the tee objects being informed.
|
||||
|
||||
``tee`` iterators are not threadsafe. A :exc:`RuntimeError` may be
|
||||
raised when using simultaneously iterators returned by the same :func:`tee`
|
||||
call, even if the original *iterable* is threadsafe.
|
||||
|
||||
This itertool may require significant auxiliary storage (depending on how
|
||||
much temporary data needs to be stored). In general, if one iterator uses
|
||||
most or all of the data before another iterator starts, it is faster to use
|
||||
|
|
|
@ -11,6 +11,7 @@ import pickle
|
|||
from functools import reduce
|
||||
import sys
|
||||
import struct
|
||||
import threading
|
||||
maxsize = support.MAX_Py_ssize_t
|
||||
minsize = -maxsize-1
|
||||
|
||||
|
@ -1494,6 +1495,42 @@ class TestBasicOps(unittest.TestCase):
|
|||
del forward, backward
|
||||
raise
|
||||
|
||||
def test_tee_reenter(self):
|
||||
class I:
|
||||
first = True
|
||||
def __iter__(self):
|
||||
return self
|
||||
def __next__(self):
|
||||
first = self.first
|
||||
self.first = False
|
||||
if first:
|
||||
return next(b)
|
||||
|
||||
a, b = tee(I())
|
||||
with self.assertRaisesRegex(RuntimeError, "tee"):
|
||||
next(a)
|
||||
|
||||
def test_tee_concurrent(self):
|
||||
start = threading.Event()
|
||||
finish = threading.Event()
|
||||
class I:
|
||||
def __iter__(self):
|
||||
return self
|
||||
def __next__(self):
|
||||
start.set()
|
||||
finish.wait()
|
||||
|
||||
a, b = tee(I())
|
||||
thread = threading.Thread(target=next, args=[a])
|
||||
thread.start()
|
||||
try:
|
||||
start.wait()
|
||||
with self.assertRaisesRegex(RuntimeError, "tee"):
|
||||
next(b)
|
||||
finally:
|
||||
finish.set()
|
||||
thread.join()
|
||||
|
||||
def test_StopIteration(self):
|
||||
self.assertRaises(StopIteration, next, zip())
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Fixed a crash in the :func:`tee` iterator when re-enter it. RuntimeError is
|
||||
now raised in this case.
|
|
@ -443,6 +443,7 @@ typedef struct {
|
|||
PyObject_HEAD
|
||||
PyObject *it;
|
||||
int numread; /* 0 <= numread <= LINKCELLS */
|
||||
int running;
|
||||
PyObject *nextlink;
|
||||
PyObject *(values[LINKCELLS]);
|
||||
} teedataobject;
|
||||
|
@ -465,6 +466,7 @@ teedataobject_newinternal(PyObject *it)
|
|||
if (tdo == NULL)
|
||||
return NULL;
|
||||
|
||||
tdo->running = 0;
|
||||
tdo->numread = 0;
|
||||
tdo->nextlink = NULL;
|
||||
Py_INCREF(it);
|
||||
|
@ -493,7 +495,14 @@ teedataobject_getitem(teedataobject *tdo, int i)
|
|||
else {
|
||||
/* this is the lead iterator, so fetch more data */
|
||||
assert(i == tdo->numread);
|
||||
if (tdo->running) {
|
||||
PyErr_SetString(PyExc_RuntimeError,
|
||||
"cannot re-enter the tee iterator");
|
||||
return NULL;
|
||||
}
|
||||
tdo->running = 1;
|
||||
value = PyIter_Next(tdo->it);
|
||||
tdo->running = 0;
|
||||
if (value == NULL)
|
||||
return NULL;
|
||||
tdo->numread++;
|
||||
|
|
Loading…
Reference in New Issue