summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSerhiy Storchaka <storchaka@gmail.com>2019-09-09 09:38:05 (GMT)
committerGitHub <noreply@github.com>2019-09-09 09:38:05 (GMT)
commit2fb6921ab296f933caf361a662e6471e143abefc (patch)
tree7d2241b4fd7d9f6809a5de02cbf8c0d6c1806832
parent0229b56d8c0cb65b8ad789e69dcd281fd92a6d96 (diff)
downloadcpython-2fb6921ab296f933caf361a662e6471e143abefc.zip
cpython-2fb6921ab296f933caf361a662e6471e143abefc.tar.gz
cpython-2fb6921ab296f933caf361a662e6471e143abefc.tar.bz2
[2.7] bpo-34410: Fix a crash in the tee iterator when re-enter it. (GH-15625) (GH-15740)
RuntimeError is now raised in this case. (cherry picked from commit 526a01467b3277f9fcf7f91e66c23321caa1245d)
-rw-r--r--Doc/library/itertools.rst4
-rw-r--r--Lib/test/test_itertools.py41
-rw-r--r--Misc/NEWS.d/next/Library/2019-08-31-01-52-59.bpo-34410.7KbWZQ.rst2
-rw-r--r--Modules/itertoolsmodule.c9
4 files changed, 56 insertions, 0 deletions
diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst
index 17303dd..18477f1 100644
--- a/Doc/library/itertools.rst
+++ b/Doc/library/itertools.rst
@@ -659,6 +659,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
diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py
index 0427979..2cdcbb2 100644
--- a/Lib/test/test_itertools.py
+++ b/Lib/test/test_itertools.py
@@ -10,6 +10,10 @@ import random
import copy
import pickle
from functools import reduce
+try:
+ import threading
+except ImportError:
+ threading = None
maxsize = test_support.MAX_Py_ssize_t
minsize = -maxsize-1
@@ -984,6 +988,43 @@ 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.assertRaisesRegexp(RuntimeError, "tee"):
+ next(a)
+
+ @unittest.skipUnless(threading, 'Threading required for this test.')
+ 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.assertRaisesRegexp(RuntimeError, "tee"):
+ next(b)
+ finally:
+ finish.set()
+ thread.join()
+
def test_StopIteration(self):
self.assertRaises(StopIteration, izip().next)
diff --git a/Misc/NEWS.d/next/Library/2019-08-31-01-52-59.bpo-34410.7KbWZQ.rst b/Misc/NEWS.d/next/Library/2019-08-31-01-52-59.bpo-34410.7KbWZQ.rst
new file mode 100644
index 0000000..64e778e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-08-31-01-52-59.bpo-34410.7KbWZQ.rst
@@ -0,0 +1,2 @@
+Fixed a crash in the :func:`tee` iterator when re-enter it. RuntimeError is
+now raised in this case.
diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c
index 04076fd..edd21be 100644
--- a/Modules/itertoolsmodule.c
+++ b/Modules/itertoolsmodule.c
@@ -314,6 +314,7 @@ typedef struct {
PyObject_HEAD
PyObject *it;
int numread;
+ int running;
PyObject *nextlink;
PyObject *(values[LINKCELLS]);
} teedataobject;
@@ -336,6 +337,7 @@ teedataobject_new(PyObject *it)
if (tdo == NULL)
return NULL;
+ tdo->running = 0;
tdo->numread = 0;
tdo->nextlink = NULL;
Py_INCREF(it);
@@ -364,7 +366,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++;