summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/re.rst2
-rw-r--r--Doc/library/time.rst4
-rw-r--r--Doc/library/xmlrpc.client.rst11
-rw-r--r--Lib/test/test_exceptions.py62
-rw-r--r--Lib/test/test_io.py52
-rw-r--r--Misc/NEWS9
-rw-r--r--Modules/_io/bufferedio.c117
-rw-r--r--Objects/genobject.c11
-rwxr-xr-xconfigure1
-rw-r--r--configure.in1
10 files changed, 200 insertions, 70 deletions
diff --git a/Doc/library/re.rst b/Doc/library/re.rst
index 3046755..540a209 100644
--- a/Doc/library/re.rst
+++ b/Doc/library/re.rst
@@ -635,7 +635,7 @@ form.
of *pattern* in *string* by the replacement *repl*. If the pattern isn't found,
*string* is returned unchanged. *repl* can be a string or a function; if it is
a string, any backslash escapes in it are processed. That is, ``\n`` is
- converted to a single newline character, ``\r`` is converted to a linefeed, and
+ converted to a single newline character, ``\r`` is converted to a carriage return, and
so forth. Unknown escapes such as ``\j`` are left alone. Backreferences, such
as ``\6``, are replaced with the substring matched by group 6 in the pattern.
For example:
diff --git a/Doc/library/time.rst b/Doc/library/time.rst
index 28e994c..7c464ac 100644
--- a/Doc/library/time.rst
+++ b/Doc/library/time.rst
@@ -553,6 +553,6 @@ The module defines the following functions and data items:
preferred hour/minute offset is not supported by all ANSI C libraries. Also, a
strict reading of the original 1982 :rfc:`822` standard calls for a two-digit
year (%y rather than %Y), but practice moved to 4-digit years long before the
- year 2000. The 4-digit year has been mandated by :rfc:`2822`, which obsoletes
- :rfc:`822`.
+ year 2000. After that, :rfc:`822` became obsolete and the 4-digit year has
+ been first recommended by :rfc:`1123` and then mandated by :rfc:`2822`.
diff --git a/Doc/library/xmlrpc.client.rst b/Doc/library/xmlrpc.client.rst
index bb058df..e72770a 100644
--- a/Doc/library/xmlrpc.client.rst
+++ b/Doc/library/xmlrpc.client.rst
@@ -402,8 +402,8 @@ by providing an invalid URI::
MultiCall Objects
-----------------
-In http://www.xmlrpc.com/discuss/msgReader%241208, an approach is presented to
-encapsulate multiple calls to a remote server into a single request.
+The :class:`MultiCall` object provides a way to encapsulate multiple calls to a
+remote server into a single request [#]_.
.. class:: MultiCall(server)
@@ -534,3 +534,10 @@ Example of Client and Server Usage
See :ref:`simplexmlrpcserver-example`.
+.. rubric:: Footnotes
+
+.. [#] This approach has been first presented in `a discussion on xmlrpc.com
+ <http://web.archive.org/web/20060624230303/http://www.xmlrpc.com/discuss/msgReader$1208?mode=topic>`_.
+.. the link now points to webarchive since the one at
+.. http://www.xmlrpc.com/discuss/msgReader%241208 is broken (and webadmin
+.. doesn't reply)
diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py
index 46ddc82..d3c0062 100644
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -607,6 +607,68 @@ class ExceptionTests(unittest.TestCase):
gc_collect()
self.assertEqual(sys.exc_info(), (None, None, None))
+ def _check_generator_cleanup_exc_state(self, testfunc):
+ # Issue #12791: exception state is cleaned up as soon as a generator
+ # is closed (reference cycles are broken).
+ class MyException(Exception):
+ def __init__(self, obj):
+ self.obj = obj
+ class MyObj:
+ pass
+
+ def raising_gen():
+ try:
+ raise MyException(obj)
+ except MyException:
+ yield
+
+ obj = MyObj()
+ wr = weakref.ref(obj)
+ g = raising_gen()
+ next(g)
+ testfunc(g)
+ g = obj = None
+ obj = wr()
+ self.assertIs(obj, None)
+
+ def test_generator_throw_cleanup_exc_state(self):
+ def do_throw(g):
+ try:
+ g.throw(RuntimeError())
+ except RuntimeError:
+ pass
+ self._check_generator_cleanup_exc_state(do_throw)
+
+ def test_generator_close_cleanup_exc_state(self):
+ def do_close(g):
+ g.close()
+ self._check_generator_cleanup_exc_state(do_close)
+
+ def test_generator_del_cleanup_exc_state(self):
+ def do_del(g):
+ g = None
+ self._check_generator_cleanup_exc_state(do_del)
+
+ def test_generator_next_cleanup_exc_state(self):
+ def do_next(g):
+ try:
+ next(g)
+ except StopIteration:
+ pass
+ else:
+ self.fail("should have raised StopIteration")
+ self._check_generator_cleanup_exc_state(do_next)
+
+ def test_generator_send_cleanup_exc_state(self):
+ def do_send(g):
+ try:
+ g.send(None)
+ except StopIteration:
+ pass
+ else:
+ self.fail("should have raised StopIteration")
+ self._check_generator_cleanup_exc_state(do_send)
+
def test_3114(self):
# Bug #3114: in its destructor, MyObject retrieves a pointer to
# obsolete and/or deallocated objects.
diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py
index 0ca621c..72c9a2d 100644
--- a/Lib/test/test_io.py
+++ b/Lib/test/test_io.py
@@ -1395,15 +1395,18 @@ class BufferedRandomTest(BufferedReaderTest, BufferedWriterTest):
rw.seek(0, 0)
self.assertEqual(b"asdf", rw.read(4))
- rw.write(b"asdf")
+ rw.write(b"123f")
rw.seek(0, 0)
- self.assertEqual(b"asdfasdfl", rw.read())
+ self.assertEqual(b"asdf123fl", rw.read())
self.assertEqual(9, rw.tell())
rw.seek(-4, 2)
self.assertEqual(5, rw.tell())
rw.seek(2, 1)
self.assertEqual(7, rw.tell())
self.assertEqual(b"fl", rw.read(11))
+ rw.flush()
+ self.assertEqual(b"asdf123fl", raw.getvalue())
+
self.assertRaises(TypeError, rw.seek, 0.0)
def check_flush_and_read(self, read_func):
@@ -1548,6 +1551,43 @@ class BufferedRandomTest(BufferedReaderTest, BufferedWriterTest):
BufferedReaderTest.test_misbehaved_io(self)
BufferedWriterTest.test_misbehaved_io(self)
+ def test_interleaved_read_write(self):
+ # Test for issue #12213
+ with self.BytesIO(b'abcdefgh') as raw:
+ with self.tp(raw, 100) as f:
+ f.write(b"1")
+ self.assertEqual(f.read(1), b'b')
+ f.write(b'2')
+ self.assertEqual(f.read1(1), b'd')
+ f.write(b'3')
+ buf = bytearray(1)
+ f.readinto(buf)
+ self.assertEqual(buf, b'f')
+ f.write(b'4')
+ self.assertEqual(f.peek(1), b'h')
+ f.flush()
+ self.assertEqual(raw.getvalue(), b'1b2d3f4h')
+
+ with self.BytesIO(b'abc') as raw:
+ with self.tp(raw, 100) as f:
+ self.assertEqual(f.read(1), b'a')
+ f.write(b"2")
+ self.assertEqual(f.read(1), b'c')
+ f.flush()
+ self.assertEqual(raw.getvalue(), b'a2c')
+
+ def test_interleaved_readline_write(self):
+ with self.BytesIO(b'ab\ncdef\ng\n') as raw:
+ with self.tp(raw) as f:
+ f.write(b'1')
+ self.assertEqual(f.readline(), b'b\n')
+ f.write(b'2')
+ self.assertEqual(f.readline(), b'def\n')
+ f.write(b'3')
+ self.assertEqual(f.readline(), b'\n')
+ f.flush()
+ self.assertEqual(raw.getvalue(), b'1b\n2def\n3\n')
+
# You can't construct a BufferedRandom over a non-seekable stream.
test_unseekable = None
@@ -2796,11 +2836,11 @@ class SignalsTest(unittest.TestCase):
os.close(w)
os.close(r)
- def test_interrupterd_read_retry_buffered(self):
+ def test_interrupted_read_retry_buffered(self):
self.check_interrupted_read_retry(lambda x: x.decode('latin1'),
mode="rb")
- def test_interrupterd_read_retry_text(self):
+ def test_interrupted_read_retry_text(self):
self.check_interrupted_read_retry(lambda x: x,
mode="r")
@@ -2859,10 +2899,10 @@ class SignalsTest(unittest.TestCase):
if e.errno != errno.EBADF:
raise
- def test_interrupterd_write_retry_buffered(self):
+ def test_interrupted_write_retry_buffered(self):
self.check_interrupted_write_retry(b"x", mode="wb")
- def test_interrupterd_write_retry_text(self):
+ def test_interrupted_write_retry_text(self):
self.check_interrupted_write_retry("x", mode="w", encoding="latin1")
diff --git a/Misc/NEWS b/Misc/NEWS
index a62b485..da0e67f 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,12 +10,21 @@ What's New in Python 3.2.3?
Core and Builtins
-----------------
+- Issue #12791: Break reference cycles early when a generator exits with
+ an exception.
+
- Issue #12266: Fix str.capitalize() to correctly uppercase/lowercase
titlecased and cased non-letter characters.
Library
-------
+- Issue #12213: Fix a buffering bug with interleaved reads and writes that
+ could appear on BufferedRandom streams.
+
+- Issue #12326: sys.platform is now always 'linux2' on Linux, even if Python
+ is compiled on Linux 3.
+
- Issue #12650: Fix a race condition where a subprocess.Popen could leak
resources (FD/zombie) when killed at the wrong time.
diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c
index 26ebde0..d6f0c9c 100644
--- a/Modules/_io/bufferedio.c
+++ b/Modules/_io/bufferedio.c
@@ -752,25 +752,38 @@ _trap_eintr(void)
*/
static PyObject *
-buffered_flush(buffered *self, PyObject *args)
+buffered_flush_and_rewind_unlocked(buffered *self)
{
PyObject *res;
- CHECK_INITIALIZED(self)
- CHECK_CLOSED(self, "flush of closed file")
-
- if (!ENTER_BUFFERED(self))
- return NULL;
res = _bufferedwriter_flush_unlocked(self, 0);
- if (res != NULL && self->readable) {
+ if (res == NULL)
+ return NULL;
+ Py_DECREF(res);
+
+ if (self->readable) {
/* Rewind the raw stream so that its position corresponds to
the current logical position. */
Py_off_t n;
n = _buffered_raw_seek(self, -RAW_OFFSET(self), 1);
- if (n == -1)
- Py_CLEAR(res);
_bufferedreader_reset_buf(self);
+ if (n == -1)
+ return NULL;
}
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+buffered_flush(buffered *self, PyObject *args)
+{
+ PyObject *res;
+
+ CHECK_INITIALIZED(self)
+ CHECK_CLOSED(self, "flush of closed file")
+
+ if (!ENTER_BUFFERED(self))
+ return NULL;
+ res = buffered_flush_and_rewind_unlocked(self);
LEAVE_BUFFERED(self)
return res;
@@ -791,7 +804,7 @@ buffered_peek(buffered *self, PyObject *args)
return NULL;
if (self->writable) {
- res = _bufferedwriter_flush_unlocked(self, 1);
+ res = buffered_flush_and_rewind_unlocked(self);
if (res == NULL)
goto end;
Py_CLEAR(res);
@@ -826,19 +839,18 @@ buffered_read(buffered *self, PyObject *args)
if (!ENTER_BUFFERED(self))
return NULL;
res = _bufferedreader_read_all(self);
- LEAVE_BUFFERED(self)
}
else {
res = _bufferedreader_read_fast(self, n);
- if (res == Py_None) {
- Py_DECREF(res);
- if (!ENTER_BUFFERED(self))
- return NULL;
- res = _bufferedreader_read_generic(self, n);
- LEAVE_BUFFERED(self)
- }
+ if (res != Py_None)
+ return res;
+ Py_DECREF(res);
+ if (!ENTER_BUFFERED(self))
+ return NULL;
+ res = _bufferedreader_read_generic(self, n);
}
+ LEAVE_BUFFERED(self)
return res;
}
@@ -864,13 +876,6 @@ buffered_read1(buffered *self, PyObject *args)
if (!ENTER_BUFFERED(self))
return NULL;
- if (self->writable) {
- res = _bufferedwriter_flush_unlocked(self, 1);
- if (res == NULL)
- goto end;
- Py_CLEAR(res);
- }
-
/* Return up to n bytes. If at least one byte is buffered, we
only return buffered bytes. Otherwise, we do one raw read. */
@@ -890,6 +895,13 @@ buffered_read1(buffered *self, PyObject *args)
goto end;
}
+ if (self->writable) {
+ res = buffered_flush_and_rewind_unlocked(self);
+ if (res == NULL)
+ goto end;
+ Py_DECREF(res);
+ }
+
/* Fill the buffer from the raw stream, and copy it to the result. */
_bufferedreader_reset_buf(self);
r = _bufferedreader_fill_buffer(self);
@@ -912,24 +924,10 @@ end:
static PyObject *
buffered_readinto(buffered *self, PyObject *args)
{
- PyObject *res = NULL;
-
CHECK_INITIALIZED(self)
- /* TODO: use raw.readinto() instead! */
- if (self->writable) {
- if (!ENTER_BUFFERED(self))
- return NULL;
- res = _bufferedwriter_flush_unlocked(self, 0);
- LEAVE_BUFFERED(self)
- if (res == NULL)
- goto end;
- Py_DECREF(res);
- }
- res = bufferediobase_readinto((PyObject *)self, args);
-
-end:
- return res;
+ /* TODO: use raw.readinto() (or a direct copy from our buffer) instead! */
+ return bufferediobase_readinto((PyObject *)self, args);
}
static PyObject *
@@ -967,12 +965,6 @@ _buffered_readline(buffered *self, Py_ssize_t limit)
goto end_unlocked;
/* Now we try to get some more from the raw stream */
- if (self->writable) {
- res = _bufferedwriter_flush_unlocked(self, 1);
- if (res == NULL)
- goto end;
- Py_CLEAR(res);
- }
chunks = PyList_New(0);
if (chunks == NULL)
goto end;
@@ -986,9 +978,16 @@ _buffered_readline(buffered *self, Py_ssize_t limit)
}
Py_CLEAR(res);
written += n;
+ self->pos += n;
if (limit >= 0)
limit -= n;
}
+ if (self->writable) {
+ PyObject *r = buffered_flush_and_rewind_unlocked(self);
+ if (r == NULL)
+ goto end;
+ Py_DECREF(r);
+ }
for (;;) {
_bufferedreader_reset_buf(self);
@@ -1157,20 +1156,11 @@ buffered_truncate(buffered *self, PyObject *args)
return NULL;
if (self->writable) {
- res = _bufferedwriter_flush_unlocked(self, 0);
+ res = buffered_flush_and_rewind_unlocked(self);
if (res == NULL)
goto end;
Py_CLEAR(res);
}
- if (self->readable) {
- if (pos == Py_None) {
- /* Rewind the raw stream so that its position corresponds to
- the current logical position. */
- if (_buffered_raw_seek(self, -RAW_OFFSET(self), 1) == -1)
- goto end;
- }
- _bufferedreader_reset_buf(self);
- }
res = PyObject_CallMethodObjArgs(self->raw, _PyIO_str_truncate, pos, NULL);
if (res == NULL)
goto end;
@@ -1367,17 +1357,18 @@ _bufferedreader_read_all(buffered *self)
Py_DECREF(chunks);
return NULL;
}
+ self->pos += current_size;
}
- _bufferedreader_reset_buf(self);
/* We're going past the buffer's bounds, flush it */
if (self->writable) {
- res = _bufferedwriter_flush_unlocked(self, 1);
+ res = buffered_flush_and_rewind_unlocked(self);
if (res == NULL) {
Py_DECREF(chunks);
return NULL;
}
Py_CLEAR(res);
}
+ _bufferedreader_reset_buf(self);
while (1) {
if (data) {
if (PyList_Append(chunks, data) < 0) {
@@ -1460,6 +1451,14 @@ _bufferedreader_read_generic(buffered *self, Py_ssize_t n)
memcpy(out, self->buffer + self->pos, current_size);
remaining -= current_size;
written += current_size;
+ self->pos += current_size;
+ }
+ /* Flush the write buffer if necessary */
+ if (self->writable) {
+ PyObject *r = buffered_flush_and_rewind_unlocked(self);
+ if (r == NULL)
+ goto error;
+ Py_DECREF(r);
}
_bufferedreader_reset_buf(self);
while (remaining > 0) {
diff --git a/Objects/genobject.c b/Objects/genobject.c
index 3fa1b4e..01cd44a 100644
--- a/Objects/genobject.c
+++ b/Objects/genobject.c
@@ -100,6 +100,17 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc)
if (!result || f->f_stacktop == NULL) {
/* generator can't be rerun, so release the frame */
+ /* first clean reference cycle through stored exception traceback */
+ PyObject *t, *v, *tb;
+ t = f->f_exc_type;
+ v = f->f_exc_value;
+ tb = f->f_exc_traceback;
+ f->f_exc_type = NULL;
+ f->f_exc_value = NULL;
+ f->f_exc_traceback = NULL;
+ Py_XDECREF(t);
+ Py_XDECREF(v);
+ Py_XDECREF(tb);
Py_DECREF(f);
gen->gi_frame = NULL;
}
diff --git a/configure b/configure
index 508ae34..9d85e35 100755
--- a/configure
+++ b/configure
@@ -2997,6 +2997,7 @@ then
MACHDEP="$ac_md_system$ac_md_release"
case $MACHDEP in
+ linux*) MACHDEP="linux2";;
cygwin*) MACHDEP="cygwin";;
darwin*) MACHDEP="darwin";;
irix646) MACHDEP="irix6";;
diff --git a/configure.in b/configure.in
index c4ad4e8..3e60d8e 100644
--- a/configure.in
+++ b/configure.in
@@ -290,6 +290,7 @@ then
MACHDEP="$ac_md_system$ac_md_release"
case $MACHDEP in
+ linux*) MACHDEP="linux2";;
cygwin*) MACHDEP="cygwin";;
darwin*) MACHDEP="darwin";;
irix646) MACHDEP="irix6";;