Commit 526a0146 authored by Serhiy Storchaka's avatar Serhiy Storchaka Committed by GitHub

bpo-34410: Fix a crash in the tee iterator when re-enter it. (GH-15625)

RuntimeError is now raised in this case.
parent 918b468b
...@@ -645,6 +645,10 @@ loops that truncate the stream. ...@@ -645,6 +645,10 @@ loops that truncate the stream.
used anywhere else; otherwise, the *iterable* could get advanced without used anywhere else; otherwise, the *iterable* could get advanced without
the tee objects being informed. 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 This itertool may require significant auxiliary storage (depending on how
much temporary data needs to be stored). In general, if one iterator uses 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 most or all of the data before another iterator starts, it is faster to use
......
...@@ -11,6 +11,7 @@ import pickle ...@@ -11,6 +11,7 @@ import pickle
from functools import reduce from functools import reduce
import sys import sys
import struct import struct
import threading
maxsize = support.MAX_Py_ssize_t maxsize = support.MAX_Py_ssize_t
minsize = -maxsize-1 minsize = -maxsize-1
...@@ -1494,6 +1495,42 @@ class TestBasicOps(unittest.TestCase): ...@@ -1494,6 +1495,42 @@ class TestBasicOps(unittest.TestCase):
del forward, backward del forward, backward
raise 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): def test_StopIteration(self):
self.assertRaises(StopIteration, next, zip()) self.assertRaises(StopIteration, next, zip())
......
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 { ...@@ -443,6 +443,7 @@ typedef struct {
PyObject_HEAD PyObject_HEAD
PyObject *it; PyObject *it;
int numread; /* 0 <= numread <= LINKCELLS */ int numread; /* 0 <= numread <= LINKCELLS */
int running;
PyObject *nextlink; PyObject *nextlink;
PyObject *(values[LINKCELLS]); PyObject *(values[LINKCELLS]);
} teedataobject; } teedataobject;
...@@ -465,6 +466,7 @@ teedataobject_newinternal(PyObject *it) ...@@ -465,6 +466,7 @@ teedataobject_newinternal(PyObject *it)
if (tdo == NULL) if (tdo == NULL)
return NULL; return NULL;
tdo->running = 0;
tdo->numread = 0; tdo->numread = 0;
tdo->nextlink = NULL; tdo->nextlink = NULL;
Py_INCREF(it); Py_INCREF(it);
...@@ -493,7 +495,14 @@ teedataobject_getitem(teedataobject *tdo, int i) ...@@ -493,7 +495,14 @@ teedataobject_getitem(teedataobject *tdo, int i)
else { else {
/* this is the lead iterator, so fetch more data */ /* this is the lead iterator, so fetch more data */
assert(i == tdo->numread); 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); value = PyIter_Next(tdo->it);
tdo->running = 0;
if (value == NULL) if (value == NULL)
return NULL; return NULL;
tdo->numread++; tdo->numread++;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment