diff options
-rw-r--r-- | Doc/library/re.rst | 2 | ||||
-rw-r--r-- | Doc/library/time.rst | 4 | ||||
-rw-r--r-- | Doc/library/xmlrpc.client.rst | 11 | ||||
-rw-r--r-- | Lib/test/test_exceptions.py | 62 | ||||
-rw-r--r-- | Lib/test/test_io.py | 52 | ||||
-rw-r--r-- | Misc/NEWS | 9 | ||||
-rw-r--r-- | Modules/_io/bufferedio.c | 117 | ||||
-rw-r--r-- | Objects/genobject.c | 11 | ||||
-rwxr-xr-x | configure | 1 | ||||
-rw-r--r-- | configure.in | 1 |
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") @@ -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; } @@ -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";; |