This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Unsupported provider

classification
Title: bytearrays are not thread safe
Type: crash Stage:
Components: Interpreter Core Versions: Python 3.0, Python 2.6
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: Nosy List: amaury.forgeotdarc, barry, benjamin.peterson, boya, donmez, gpolo, lemburg, loewis, pitrou, teoliphant
Priority: release blocker Keywords: patch

Created on 2008-06-19 10:06 by amaury.forgeotdarc, last changed 2022-04-11 14:56 by admin. This issue is now closed.

Files
File name Uploaded Description Edit
test_threaded_print.diff amaury.forgeotdarc, 2008-06-19 10:06 Modification of test_queue that shows print problems
bzcrash.patch pitrou, 2008-07-05 22:48
s_star.diff loewis, 2008-08-01 15:23
s_star2.patch pitrou, 2008-08-02 22:48
refcount.diff loewis, 2008-08-14 18:52
Messages (64)
msg68397 - (view) Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) * (Python committer) Date: 2008-06-19 10:06
I found this problem when adding "print" statements to multi-threaded
code. When applying the attached diff to a py3k installation, the output
on screen always contains some garbage.

The following code is an extract of fileio_write (in Modules/_fileio.c),
but the same behavior appears everywhere:

	if (!PyArg_ParseTuple(args, "s#", &ptr, &n))
		return NULL;

	Py_BEGIN_ALLOW_THREADS
	errno = 0;
	n = write(self->fd, ptr, n);
	Py_END_ALLOW_THREADS

io.BufferedWriter calls this function with a bytearray.
In this case, the GIL is released when holding a pointer to the
bytearray data.
But another thread may mutate the bytearray in between, the pointer
becomes stale and can lead to segfaults or funny results.
msg68398 - (view) Author: Guilherme Polo (gpolo) * (Python committer) Date: 2008-06-19 11:03
Wasn't that always known ? Maybe I'm missing something here.
msg68400 - (view) Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) * (Python committer) Date: 2008-06-19 12:28
The problem is not only about concurrent prints.
It is about invalid pointer passed to a C function.
Here is an example that reliably crashes the interpreter on my windows
machine:

import bz2, threading
bz2c = bz2.BZ2Compressor()
b = bytearray(b"a" * 1000000)
def f():
    for x in range(10):
        b[:] = b""
        b[:] = bytearray(b"a" * 1000000)
threading.Thread(target=f).start()
for x in range(10):
    bz2c.compress(b)

bz2c.compress is a slow function, that happens to accept bytearray and
to release the GIL. If the other thread reallocates the bytearray,
bz2c.compress will read invalid data.
msg69220 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-07-03 18:08
Wow...
Isn't this kind of code supposed to ask for a buffer on the bytearray
object, together with an optional lock on it (or something like that)?
msg69245 - (view) Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) * (Python committer) Date: 2008-07-04 06:29
Probably, but this affects all PyArg_ParseTuple("s#") calls that release
the GIL afterwards. How many of them are there?
msg69286 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-07-05 18:35
If I try to follow the chain the consequences:
 - all PyArg_ParseTuple("s#") calls that release the GIL afterwards
should be re-written to use another API (which one I don't know exactly,
but hopefully the appropriate functions are already provided by the
buffer API); this applies to third-party extension modules as well
 - consequently, forward compatibility is broken in an important way
(but it would probably be ok for py3k)
 - perhaps the bytearray type should not have been backported to 2.6, or
perhaps it should carry a big warning in the documentation
msg69287 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-07-05 18:39
By the way, here's a more reliable way to make it crash (on my Linux
machine):


import bz2, threading

bz2c = bz2.BZ2Compressor()
# Span at least a whole arena (256KB long)
junk_len = 512 * 1024
junk = b"a" * junk_len
buf = bytearray()

for x in range(50):
    buf[:] = junk
    t = threading.Thread(target=bz2c.compress, args=[buf])
    t.start()
    buf[:] = b""
    t.join()
msg69288 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-07-05 18:48
Now I've just discovered there is the same problem with the
array.array() type (see following code).


import bz2, threading, array

bz2c = bz2.BZ2Compressor()
# Span at least a whole arena (256KB long)
junk_len = 512 * 1024
junk = array.array("c", b"a") * junk_len
empty = array.array("c")
buf = empty[:]

for x in range(50):
    buf[:] = junk
    t = threading.Thread(target=bz2c.compress, args=[buf])
    t.start()
    buf[:] = empty
    t.join()
msg69304 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2008-07-05 22:20
I believe the 2.6 s# processing works correctly; the error is in the
bytearray object. This either shouldn't support the buffer interface, or
it shouldn't reallocate the buffer after having returned a pointer to it.

For 3k, convertbuffer shouldn't call bf_releasebuffer; instead, the
caller of ParseTuple somehow needs to release the buffer. As a
consequence, s# should refuse buffer objects who have a bf_releasebuffer
operation, and another code might get added to fill out a Py_buffer - or
s# can get changed to always produce a Py_buffer (which would affect the
code significantly).
msg69307 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-07-05 22:38
Le samedi 05 juillet 2008 à 22:20 +0000, Martin v. Löwis a écrit :
> Martin v. Löwis <martin@v.loewis.de> added the comment:
> 
> I believe the 2.6 s# processing works correctly; the error is in the
> bytearray object. This either shouldn't support the buffer interface, or
> it shouldn't reallocate the buffer after having returned a pointer to it.

getbuffer and releasebuffer exist in both 2.6 and 3.0, and bytearray
supports those methods in both.

As for array.array, it only implements old buffer API.
msg69309 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-07-05 22:47
For reference, here is a proof-of-concept patch which shows how to fix
the bytearray crasher above (it raises a BufferError instead).
msg69318 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2008-07-06 06:28
Fixing this in the methods themselves has the disadvantage that the
error reporting is not that good anymore.
msg69774 - (view) Author: Barry A. Warsaw (barry) * (Python committer) Date: 2008-07-16 12:33
Not blocking beta 2 because there's not enough time for the fix, but
this will definitely block beta 3.
msg70212 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2008-07-24 16:03
Here is a patch adding the s* format, and changing files, sockets, and
fileio to use it. For bz2, the immediate effect is that you get a type
error (as an object providing bf_releasebuffer cannot be converted
through s#/w# anymore); it would be possible to fix bz2 also to use
s*/w* instead.

I'd like reviewers to focus on flaws in the approach and bugs in the
implementation; existing code should be converted to the new API
afterwards (or not converted at all for 2.6/3.0, but only as patches get
contributed).

If this is accepted in principle, I'll forward-port it to 3.0.
msg70243 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-07-25 09:16
I haven't yet studied the patch in detail but I have a few questions:

(1) are you sure it is safe not to INCREF the obj pointer in the
Py_buffer? in the cases handled by your patch we still hold a reference
to the original args tuple and dict before calling PyBuffer_Release(),
but some functions may want to store the Py_buffer object somewhere to
re-use it later. It would seem more logical for PyBuffer_FillInfo to
INCREF the obj, and for PyBuffer_Release to DECREF it and set it to NULL.

(2) is it necessary to call directly bf_getbuffer & the like or is there
a higher-level API to do it?

(3) didn't you forget to convert "PyArg_ParseTuple(args, "s#iO:sendto",
[...])" in sock_sendto?

(4) is it really necessary to do a special case with PyString_Check()
rather than rely on the string type's getbuffer method?
msg70244 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-07-25 09:46
Travis, it would be really nice to have your input on this.
msg70301 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2008-07-26 13:07
> (1) are you sure it is safe not to INCREF the obj pointer in the
> Py_buffer?

Yes, that's the semantics of the current buffer interface, and I cannot
see a flaw in it. When you call getbuffer, you certainly hold a
reference, and it is your obligation to hold onto this reference
somehow. So it is definitely safe (if properly documented).

> It would seem more logical for PyBuffer_FillInfo to
> INCREF the obj, and for PyBuffer_Release to DECREF it and set it to NULL.

Perhaps. I cannot quite see what undesirable consequences that might
have - feel free to develop and test an alternative patch that takes
that approach.

> (2) is it necessary to call directly bf_getbuffer & the like or is there
> a higher-level API to do it?

There is PyObject_GetBuffer and PyObject_ReleaseBuffer, but it is not
higher-level. I need to check the function pointers, anyway (to
determine whether the object supports getbuffer and requires
releasebuffer or not), so calling them directly is the right level
of abstraction (IMO).

> (3) didn't you forget to convert "PyArg_ParseTuple(args, "s#iO:sendto",
> [...])" in sock_sendto?

True.

> (4) is it really necessary to do a special case with PyString_Check()
> rather than rely on the string type's getbuffer method?

That's what the code always did (for s#). It would be possible to
eliminate that case, yes.
msg70319 - (view) Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) * (Python committer) Date: 2008-07-27 08:38
With the patch the following script crashes the 2.6 interpreter on Windows:

import sys, io, threading
stdout2 = io.open(sys.stdout.fileno(), mode="w")
def f(i):
    for i in range(10):
        stdout2.write(unicode((x, i)) + '\n')
for x in range(10):
    t = threading.Thread(target=f, args=(x,))
    t.start()

(with py3k, replace "stdout2.write" with a simple "print")
msg70432 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-07-30 16:56
The problem is that the fix for #3295 was committed in the py3k branch
(in r64751) rather thank on the trunk!
Once PyExc_BufferError is defined properly the crash disappears and
exceptions are printed instead.
msg70435 - (view) Author: Benjamin Peterson (benjamin.peterson) * (Python committer) Date: 2008-07-30 17:45
Sorry, that was my oversight! I've backported the fix.
msg70443 - (view) Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) * (Python committer) Date: 2008-07-30 23:03
It's indeed better.
Now with when running my previous script I can see the exception ;-)

Exception in thread Thread-2:
Traceback (most recent call last):
  File "C:\dev\python\trunk1\lib\threading.py", line 523, in
__bootstrap_inner
    self.run()
  File "C:\dev\python\trunk1\lib\threading.py", line 478, in run
    self.__target(*self.__args, **self.__kwargs)
  File "<stdin>", line 3, in f
  File "C:\dev\python\trunk1\lib\io.py", line 1473, in write
    self.buffer.write(b)
  File "C:\dev\python\trunk1\lib\io.py", line 1041, in write
    self._write_buf.extend(b)
BufferError: Existing exports of data: object cannot be re-sized

Again, I think this is unfortunate for a simple script that prints from
several threads.
msg70444 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-07-30 23:41
Le mercredi 30 juillet 2008 à 23:03 +0000, Amaury Forgeot d'Arc a
écrit :
> Again, I think this is unfortunate for a simple script that prints from
> several threads.

Yes, but it's an issue with the BufferedWriter implementation, since it
changes the length of its underlying bytearray object. If it was
rewritten to use a fixed-size bytearray, the problem would probably
disappear.

(in the middle term BufferedReader and BufferedWriter should perhaps be
both rewritten in C)
msg70447 - (view) Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) * (Python committer) Date: 2008-07-31 00:00
> If it was rewritten to use a fixed-size bytearray
does such an object exist today?
msg70448 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-07-31 00:16
Le jeudi 31 juillet 2008 à 00:00 +0000, Amaury Forgeot d'Arc a écrit :
> Amaury Forgeot d'Arc <amauryfa@gmail.com> added the comment:
> 
> > If it was rewritten to use a fixed-size bytearray
> does such an object exist today?

Manually, yes :)
Just preallocate your bytearray, e.g.:
    b = bytearray(b" " * 4096)

and then be careful to only do operations (e.g. slice assignments) which
keep the size intact.
In a BufferedWriter implementation, you would have to keep track of the
currently used chunk in the bytearray (offset and size).

Anyway, I'd question the efficiency of the bytearray approach; when
removing the quadratic behaviour in BufferedReader I discovered that
using a bytearray was slower than keeping a list of bytes instances and
joining them when needed (not to mention that the latter is inherently
thread-safe :-)). The reason is that the underlying raw stream expects
and returns bytes, and the public buffered API also does, so using a
bytearray internally means lots of additional memory copies.

(a related problem is that readinto() does not let you specify an offset
inside the given buffer object, it always starts writing at the
beginning of the buffer. Perhaps memoryview() will support creating
subbuffers, I don't know...)
msg70495 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-07-31 09:09
Martin, sorry for noticing this now but on python-dev you had proposed
the following:

« I propose that new codes s*, t*, w* are added, and that s#,t#,w#
refuses objects which implement a releasebuffer procedure
(alternatively, s# etc might be removed altogether right away) »

I don't see any changes to s# and t# in your patch. Do you plan to do it
later or do you think this part should be dropped?
msg70505 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2008-07-31 14:22
> I don't see any changes to s# and t# in your patch. Do you plan to do it
> later or do you think this part should be dropped?

It's implemented. When bf_releasebuffer is not NULL, convertbuffer will
complain "string or read-only buffer".
msg70515 - (view) Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) * (Python committer) Date: 2008-07-31 15:04
Ok for s#. 
But t# uses bf_getcharbuffer(), which does not seem to lock anything.
I wonder if this can lead to the same kind of problems, for example when
calling file_write with a binary file.
msg70529 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2008-07-31 20:14
> But t# uses bf_getcharbuffer(), which does not seem to lock anything.

Indeed. I think we can safely drop support for passing buffer objects
into t# which have getbuffer/releasebuffer, so the check for
releasebuffer being NULL should be added to t#.

I think the bytearray object should refuse to implement getcharbuffer,
anyway.

> I wonder if this can lead to the same kind of problems, for example when
> calling file_write with a binary file.

It should be a text file to cause problems, right?
msg70555 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2008-08-01 14:29
Amaury, I think the issue of thread-safety of the io library should be
decoupled from this issue. I don't think it is release-critical that the
io library is thread-safe (and even if it is release-critical, the
problem should be tracked in a different issue, as it requires a
completely independent patch).

The original issue (bytearrays are not thread-safe) will not be
completely resolved (for a certain definition of "thread-safe"): it will
always be possible that one thread observes a locked byte object - this
is by design of the buffer interface, and it is a good design.
msg70557 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-01 14:38
Selon "Martin v. Löwis" <report@bugs.python.org>:
>
> Amaury, I think the issue of thread-safety of the io library should be
> decoupled from this issue. I don't think it is release-critical that the
> io library is thread-safe (and even if it is release-critical, the
> problem should be tracked in a different issue, as it requires a
> completely independent patch).

Sorry Martin, I forgot to mention that I did open a separate issue in #3476.

Regards

Antoine.
msg70559 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2008-08-01 15:23
I have now updated the patch to fix the socket bug, and the rejects
bytearray for t#.

As for making Py_buffer own a reference to the object: what should be
the semantics for PyObject_ReleaseBuffer? I see the following options:
- Drop PyObject_ReleaseBuffer
- make it DECREF the embedded object, whether or not it is the same as
the object being passed in
- leave it as-is, and require the caller to DECREF.
msg70562 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-01 15:46
Selon "Martin v. Löwis" <report@bugs.python.org>:
>
> As for making Py_buffer own a reference to the object: what should be
> the semantics for PyObject_ReleaseBuffer? I see the following options:
> - Drop PyObject_ReleaseBuffer
> - make it DECREF the embedded object, whether or not it is the same as
> the object being passed in
> - leave it as-is, and require the caller to DECREF.

I don't know, is there supposed to be a semantic difference between
PyObject_ReleaseBuffer and PyBuffer_Release? If not, I'd say drop it.

Also, I think it's fine if you commit your fix without adding an incref/decref.
In the absence of the designer of the buffer API it is difficult to know what
subtleties should be taken into account when trying to change that API...
msg70575 - (view) Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) * (Python committer) Date: 2008-08-01 17:53
Martin, 

There is a small issue with the patch: in the "w#" format handler,
bf_getwritebuffer(arg, 0, res) is wrong. The third argument should be
&res (there is a compilation warning on windows),

And a few lines after, in the "if (*format == '#')" block, there should
be a line like 
     *p = res;
otherwise the user code never sees the buffer...
msg70583 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-01 18:48
Le vendredi 01 août 2008 à 17:53 +0000, Amaury Forgeot d'Arc a écrit :
> There is a small issue with the patch: in the "w#" format handler,
> bf_getwritebuffer(arg, 0, res) is wrong. The third argument should be
> &res (there is a compilation warning on windows),
> 
> And a few lines after, in the "if (*format == '#')" block, there should
> be a line like 
>      *p = res;
> otherwise the user code never sees the buffer...

Nice catch! Making those changes actually fixes a segfault I had in
testReadinto in test_file.py.

By the way, please note bytearray.decode is broken:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: ascii_decode() argument 1 must be string or pinned buffer, not bytearray
>>> bytearray(b"").decode("utf8")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/antoine/cpython/bufferedwriter/Lib/encodings/utf_8.py", line 16, in decode
    return codecs.utf_8_decode(input, errors, True)
TypeError: utf_8_decode() argument 1 must be string or pinned buffer, not bytearray
>>> bytearray(b"").decode("latin1")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: latin_1_decode() argument 1 must be string or pinned buffer, not bytearray
msg70587 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-01 19:32
I'm attaching a patch for getargs.c and another patch for the codecs
module.
msg70595 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2008-08-01 21:44
> I don't know, is there supposed to be a semantic difference between
> PyObject_ReleaseBuffer and PyBuffer_Release? If not, I'd say drop it.

There are existing callers of it which would need to be changed, perhaps
outside the core also; plus it's in PEP 3118.

Technically, they do the same.
msg70633 - (view) Author: Marc-Andre Lemburg (lemburg) * (Python committer) Date: 2008-08-02 14:32
Two comments:

 * I like the new *-getarg parameters, but it would be better to test
   for '#' first since this is still by far the most used getarg
   parameter.

 * Antoine, I think your codecs.c patch has a glitch:

+    decoded = PyUnicode_DecodeMBCSStateful(pbuf.buf, pbuf.len, errors,
+					   final ? NULL : &consumed);
+	PyBuffer_Release(&pbuf);
+    if (decoded == NULL)
 	return NULL;
-    return codec_tuple(decoded, final ? size : consumed);
+    return codec_tuple(decoded, consumed);
 }
 
   You dropped the "final ? size : " for no apparent reason.
msg70634 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-02 14:49
You've missed the preceding line that says:

+    consumed = pbuf.len;

If final is NULL, consumed isn't updated by the call to
PyUnicode_DecodeMBCSStateful and keeps its original value of pbuf.len.

(in all honesty, I didn't test under Windows so this particular function
wasn't enabled when I compiled and ran the test suite)
msg70640 - (view) Author: Marc-Andre Lemburg (lemburg) * (Python committer) Date: 2008-08-02 20:13
On 2008-08-02 16:49, Antoine Pitrou wrote:
> Antoine Pitrou <pitrou@free.fr> added the comment:
> 
> You've missed the preceding line that says:
> 
> +    consumed = pbuf.len;
> 
> If final is NULL, consumed isn't updated by the call to
> PyUnicode_DecodeMBCSStateful and keeps its original value of pbuf.len.
> 
> (in all honesty, I didn't test under Windows so this particular function
> wasn't enabled when I compiled and ran the test suite)

Thanks for clarifying this.
msg70645 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-02 22:48
Here is a new patch wrapping up Martin's patch with the following additions:
- getargs.c fixes
- codecs module fixes
- multiprocessing module fix
- fileobject readinto fix
- in bytearray deallocator, print out a SystemError if there are
existing exports

The whole test suite now passes, which is a good start :-)
msg71012 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-11 12:57
The last beta is arriving soon. We need to go ahead on this, or retarget
it for 2.7/3.1.
msg71027 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2008-08-11 18:20
> The last beta is arriving soon. We need to go ahead on this, or retarget
> it for 2.7/3.1.

I'll look into it tomorrow.

Regards,
Martin
msg71052 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2008-08-12 14:51
I have now committed the patch to 2.6 as r65654, adding changes for the
bz2module.

I also decided to make the Py_buffer structure own its reference, as I
was running out of arguments why not to. In the process, I removed
PyObject_ReleaseBuffer, as it is redundant and would have an unclear
sematics (what if the object passed directly and the object passed
indirectly were different?).
msg71059 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2008-08-12 16:15
I also started working on porting it to 3.0, but couldn't complete that
port yet - the memoryview object doesn't play nicely.
msg71093 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-13 18:07
Le mardi 12 août 2008 à 16:15 +0000, Martin v. Löwis a écrit :
> I also started working on porting it to 3.0, but couldn't complete that
> port yet - the memoryview object doesn't play nicely.

I've seen your recent merge and I don't know if you have finished with
it.

I think we should drop the "base" member in PyMemoryViewObject, it has
become redundant and confusing. There are some places in memoryobject.c
where base seems mistakingly used instead of view.obj, e.g.
PyMemoryView_FromMemory INCREFs view.obj, but memory_dealloc DECREFs
base.
Also, I don't understand why memory_getbuf INCREFs view.obj, there is no
corresponding DECREF in memory_releasebuf and view.obj should already
have been INCREFed anyway.

(if people want to get easily at the base object, we could provide be a
macro e.g. PyMemory_GET_BASE())

By the way, perhaps PyBuffer_Release should set view->obj and view->buf
to NULL (and view->len to -1?), it would be a simple way to signal that
the buffer can't be used anymore.

What do you think?
msg71098 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2008-08-13 21:03
> I've seen your recent merge and I don't know if you have finished with
> it.

Indeed, I'm done, r65654. Unless there are actual bugs in these patches
(in the sense that they don't fix the reported problem, or introduce new
bugs), I'd like to close this issue.

> I think we should drop the "base" member in PyMemoryViewObject, it has
> become redundant and confusing.

I tried, and failed; feel free to submit a patch (as a new issue).
The tricky part is that base is sometimes used as a tuple.

> Also, I don't understand why memory_getbuf INCREFs view.obj, there is no
> corresponding DECREF in memory_releasebuf and view.obj should already
> have been INCREFed anyway.

Ok, that's an open issue. Is the caller of FromMemory supposed to do
Buffer_Release afterwards, or is ownership of the buffer transferred
in the FromMemory call? (the issue didn't exist when the embedded
reference was borrowed)

To put it another way: view.obj has *not* been INCREFed. *view holds
a reference, but that reference belongs to the caller, not to the memory
object. Every time you initialize a PyObject* (such as view.obj), you
need to INCREF, unless it's a borrowed reference.

In any case, the corresponding DECREF *does* exist: in memory_dealloc,
PyBuffer_Release is invoked, which releases the reference.

> By the way, perhaps PyBuffer_Release should set view->obj and view->buf
> to NULL (and view->len to -1?), it would be a simple way to signal that
> the buffer can't be used anymore.

That can be done; it's again a separate patch.
msg71099 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-13 21:12
> In any case, the corresponding DECREF *does* exist: in memory_dealloc,
> PyBuffer_Release is invoked, which releases the reference.

I'm a bit confused. In the PyBuffer_Release implementation you
committed, there is no DECREF at all.

> > By the way, perhaps PyBuffer_Release should set view->obj and view->buf
> > to NULL (and view->len to -1?), it would be a simple way to signal that
> > the buffer can't be used anymore.
> 
> That can be done; it's again a separate patch.

Ok.
msg71134 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2008-08-14 16:13
> I'm a bit confused. In the PyBuffer_Release implementation you
> committed, there is no DECREF at all.

Oops, I meant to make the reference own by Py_buffer, but actually
forgot to implement that. Fixed in r65677, r65678.

Now, when I try to merge that into the 3k branch, test_unicode terribly
leaks memory :-( It's really frustrating.

Regards,
Martin
msg71136 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-14 17:28
Le jeudi 14 août 2008 à 16:13 +0000, Martin v. Löwis a écrit :
> Now, when I try to merge that into the 3k branch, test_unicode terribly
> leaks memory :-( It's really frustrating.

If you don't have the time to work on it anymore, you can post the
current patch here and I'll take a try.

Regards

Antoine.
msg71139 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2008-08-14 18:52
The patch is really trivial, and attached.
msg71140 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-14 18:55
Le jeudi 14 août 2008 à 18:52 +0000, Martin v. Löwis a écrit :
> The patch is really trivial, and attached.
> 
> Added file: http://bugs.python.org/file11114/refcount.diff

By the way, even without that patch, there is a memory leak:

Python 3.0b2+ (py3k, Aug 14 2008, 20:49:19) 
[GCC 4.3.1 20080626 (prerelease)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys, codecs
>>> b = bytearray()
>>> sys.getrefcount(b)
2
>>> codecs.ascii_decode(memoryview(b))
('', 0)
>>> sys.getrefcount(b)
3
msg71142 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2008-08-14 19:06
> By the way, even without that patch, there is a memory leak:

With the patch, this memory leak goes away.

Regards,
Martin
msg71145 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-14 19:30
Le jeudi 14 août 2008 à 19:06 +0000, Martin v. Löwis a écrit :
> Martin v. Löwis <martin@v.loewis.de> added the comment:
> 
> > By the way, even without that patch, there is a memory leak:
> 
> With the patch, this memory leak goes away.

But now:

30
>>> m = memoryview(b)
>>> sys.getrefcount(b)
32
>>> del m
>>> sys.getrefcount(b)
31
msg71146 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-14 19:33
Sorry, the roundup e-mail interface ate some lines of code:

>>> b = b''
>>> sys.getrefcount(b)
30
>>> m = memoryview(b)
>>> sys.getrefcount(b)
32
>>> del m
>>> sys.getrefcount(b)
31

It doesn't happen with bytearrays, so I suspect it's that you only
DECREF when releasebuffer method exists:

>>> b = bytearray()
>>> sys.getrefcount(b)
2
>>> m = memoryview(b)
>>> sys.getrefcount(b)
4
>>> del m
>>> sys.getrefcount(b)
2
msg71152 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2008-08-14 20:36
> It doesn't happen with bytearrays, so I suspect it's that you only
> DECREF when releasebuffer method exists:

Thanks, that was indeed the problem; this is now fixed in r65683 and r65685.

My initial observation that test_unicode leaks a lot of memory was
incorrect - it's rather that test_raiseMemError consumes all my
memory (probably without leaking).

test_unicode still leaks 6 references each time; one reference is leaked
whenever a SyntaxError is raised. I'm not sure though whether this was
caused by this patch, so I'll close this issue as fixed. Any further
improvements should be done through separate patches (without my
involvement, most likely).
msg71195 - (view) Author: Ismail Donmez (donmez) * Date: 2008-08-16 05:50
This seems to break test_unicode on MacOSX 10.5.4,

test_unicode
python(80320,0xa0659fa0) malloc: *** mmap(size=2147483648) failed (error 
code=12)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug
python(80320,0xa0659fa0) malloc: *** mmap(size=2147483648) failed (error 
code=12)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug
msg71480 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-19 20:44
Le samedi 16 août 2008 à 05:50 +0000, Ismail Donmez a écrit :
> This seems to break test_unicode on MacOSX 10.5.4,
> 
> test_unicode
> python(80320,0xa0659fa0) malloc: *** mmap(size=2147483648) failed (error 
> code=12)
> *** error: can't allocate region
> *** set a breakpoint in malloc_error_break to debug
> python(80320,0xa0659fa0) malloc: *** mmap(size=2147483648) failed (error 
> code=12)
> *** error: can't allocate region
> *** set a breakpoint in malloc_error_break to debug

Can you run Lib/test/test_unicode.py directly to know which test
precisely crashes?
msg71502 - (view) Author: Ismail Donmez (donmez) * Date: 2008-08-20 01:51
> Can you run Lib/test/test_unicode.py directly to know which test
> precisely crashes?

The latest py3k branch is fine now and the test passes.
msg71873 - (view) Author: Travis Oliphant (teoliphant) * (Python committer) Date: 2008-08-24 21:13
I'm sorry that I was unavailable for comment during July and August as
it looks like a lot of decisions were made that have changed the
semantics a bit.  I'm still trying to figure out why the decisions were
made that were made.   

I get the impression that most of the problems are related to objects
incorrectly managing their exported buffers, but there may be some
semantic issues related to "t#" that were not conceived of during the
many discussions surrounding the design of PEP 3118.  

I'm not convinced that Py_buffer should have grown a link to an object.
 I think this is a shortcut solution due to misuse of the protocol that
may have unfortunate consequences. 

I'm not sure where PyBuffer_Release came from.  I can't find it in the
PEP and don't remember what it's purpose is.  Did I add it or did
somebody elese?
msg71878 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-24 21:24
Hi Travis,

Glad you're back!

> I'm not convinced that Py_buffer should have grown a link to an object.
> I think this is a shortcut solution due to misuse of the protocol that
> may have unfortunate consequences. 

What consequences are you thinking about?

Specifically, why shouldn't Py_buffer have a link to the object? It's
the best way we've found to be able to release the buffer without having
to keep a link to the originator ourselves. The concern is to simplify
the API for most of its users. Especially, the new format codes ("s*" et
al.) can just fill the Py_buffer rather than return several things at
once.

(please note that link can be NULL if you don't want to have the
associated resource management)

> I'm not sure where PyBuffer_Release came from.  I can't find it in the
> PEP and don't remember what it's purpose is.

It's a replacement for PyObject_ReleaseBuffer(). Since a Py_buffer now
has a link to its originator, there's no need to pass it separately to
the releasing function.
msg92143 - (view) Author: Boya Sun (boya) Date: 2009-09-01 21:58
Although the bug is fixed, the following three code segments seems
suspicious in _codecsmodule.c in the latest revision 74624, and they are
similar to the bug described here:

(1)
escape_decode(PyObject *self,
	      PyObject *args)
{
    ...
    const char *data;
    ...
    if (!PyArg_ParseTuple(args, "s#|z:escape_decode",
			  &data, &size, &errors))
}

(2)
readbuffer_encode(PyObject *self,
		  PyObject *args)
{
    const char *data;
    ...
    if (!PyArg_ParseTuple(args, "s#|z:readbuffer_encode",
			  &data, &size, &errors))
    ...
}

(3)
charbuffer_encode(PyObject *self,
		  PyObject *args)
{
    const char *data;
    ...
    if (!PyArg_ParseTuple(args, "t#|z:charbuffer_encode",
			  &data, &size, &errors))
    ...
}

Firstly, "char *data;" have been replaced by "Py_buffer pbuf;" in many
procedures in this file in the bug fix, but these code did not;
Secondly, they uses "s#" or "t#" which should probably changed to "s*";

I could be wrong about it.  Does anyone have any opinions on the above
code? Are they really buggy or am I misunderstanding anything?
msg92148 - (view) Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) * (Python committer) Date: 2009-09-02 00:54
In theory, yes. But it happens that the GIL is not released before the 
buffer is used.
msg92168 - (view) Author: Boya Sun (boya) Date: 2009-09-02 15:01
I am still a little bit confused.  Can you explain a little more in
detail? What is the difference between the suspicious code and the ones
that are fixed?
msg92175 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2009-09-02 18:45
A problem can only occur if you preserve a pointer to the buffer,
and the object gets a chance to change its buffer underneath. This
can happen when there are user-defined callback, and when other
threads can get control. In the cases being fixed, other threads
*can* get control, as the GIL is released. In the cases you discuss,
this cannot happen, since the GIL is not released, and no codepath
can lead to buffer reallocation.
History
Date User Action Args
2022-04-11 14:56:35adminsetgithub: 47389
2009-09-02 18:45:10loewissetmessages: + msg92175
2009-09-02 15:01:25boyasetmessages: + msg92168
2009-09-02 00:54:19amaury.forgeotdarcsetmessages: + msg92148
2009-09-01 21:58:39boyasetnosy: + boya
messages: + msg92143
2008-08-24 21:24:42pitrousetmessages: + msg71878
2008-08-24 21:13:25teoliphantsetmessages: + msg71873
2008-08-20 01:51:05donmezsetmessages: + msg71502
2008-08-19 20:44:58pitrousetmessages: + msg71480
2008-08-16 05:50:15donmezsetmessages: + msg71195
2008-08-14 20:36:30loewissetstatus: open -> closed
resolution: fixed
2008-08-14 20:36:05loewissetmessages: + msg71152
2008-08-14 19:33:53pitrousetmessages: + msg71146
2008-08-14 19:30:54pitrousetmessages: + msg71145
2008-08-14 19:06:23loewissetmessages: + msg71142
2008-08-14 18:55:34pitrousetmessages: + msg71140
2008-08-14 18:52:34loewissetfiles: + refcount.diff
messages: + msg71139
2008-08-14 17:28:34pitrousetmessages: + msg71136
2008-08-14 16:13:57loewissetmessages: + msg71134
2008-08-13 21:12:30pitrousetmessages: + msg71099
2008-08-13 21:03:48loewissetmessages: + msg71098
2008-08-13 18:07:35pitrousetmessages: + msg71093
2008-08-12 16:15:32loewissetmessages: + msg71059
2008-08-12 14:51:41loewissetmessages: + msg71052
2008-08-11 18:20:25loewissetmessages: + msg71027
2008-08-11 12:57:47pitrousetmessages: + msg71012
2008-08-02 22:51:55pitrousetfiles: - getargs.patch
2008-08-02 22:51:49pitrousetfiles: - codecs.patch
2008-08-02 22:48:23pitrousetfiles: + s_star2.patch
messages: + msg70645
2008-08-02 20:13:12lemburgsetmessages: + msg70640
2008-08-02 14:49:53pitrousetmessages: + msg70634
2008-08-02 14:32:53lemburgsetnosy: + lemburg
messages: + msg70633
2008-08-01 21:44:34loewissetmessages: + msg70595
2008-08-01 19:33:07giampaolo.rodolasetnosy: - giampaolo.rodola
2008-08-01 19:32:16pitrousetfiles: + codecs.patch, getargs.patch
messages: + msg70587
2008-08-01 18:48:27pitrousetmessages: + msg70583
2008-08-01 17:53:48amaury.forgeotdarcsetmessages: + msg70575
2008-08-01 15:46:25pitrousetmessages: + msg70562
2008-08-01 15:23:46loewissetfiles: - s_star.diff
2008-08-01 15:23:33loewissetfiles: + s_star.diff
messages: + msg70559
2008-08-01 14:38:58pitrousetmessages: + msg70557
2008-08-01 14:29:08loewissetmessages: + msg70555
2008-07-31 20:14:54loewissetmessages: + msg70529
2008-07-31 15:04:31amaury.forgeotdarcsetmessages: + msg70515
2008-07-31 14:22:54loewissetmessages: + msg70505
2008-07-31 09:09:44pitrousetmessages: + msg70495
2008-07-31 00:16:35pitrousetmessages: + msg70448
2008-07-31 00:00:04amaury.forgeotdarcsetmessages: + msg70447
2008-07-30 23:41:54pitrousetmessages: + msg70444
2008-07-30 23:03:52amaury.forgeotdarcsetmessages: + msg70443
2008-07-30 17:45:43benjamin.petersonsetnosy: + benjamin.peterson
messages: + msg70435
2008-07-30 16:56:03pitrousetmessages: + msg70432
2008-07-27 08:38:03amaury.forgeotdarcsetmessages: + msg70319
2008-07-26 13:07:33loewissetmessages: + msg70301
2008-07-25 09:46:47pitrousetnosy: + teoliphant
messages: + msg70244
2008-07-25 09:16:07pitrousetmessages: + msg70243
2008-07-24 16:04:02loewissetfiles: + s_star.diff
messages: + msg70212
2008-07-18 03:46:29barrysetpriority: deferred blocker -> release blocker
2008-07-16 12:33:01barrysetpriority: release blocker -> deferred blocker
nosy: + barry
messages: + msg69774
2008-07-06 17:25:12loewissetpriority: release blocker
2008-07-06 07:32:24donmezsetnosy: + donmez
2008-07-06 06:28:32loewissetmessages: + msg69318
2008-07-05 22:48:03pitrousetfiles: + bzcrash.patch
2008-07-05 22:47:54pitrousetfiles: - bzcrash.py
2008-07-05 22:47:18pitrousetfiles: + bzcrash.py
messages: + msg69309
2008-07-05 22:38:59pitrousetmessages: + msg69307
2008-07-05 22:20:58loewissetnosy: + loewis
messages: + msg69304
2008-07-05 18:48:15pitrousetmessages: + msg69288
2008-07-05 18:39:18pitrousetmessages: + msg69287
2008-07-05 18:35:48pitrousetmessages: + msg69286
2008-07-04 06:29:47amaury.forgeotdarcsetmessages: + msg69245
2008-07-03 18:08:28pitrousetnosy: + pitrou
messages: + msg69220
2008-06-24 12:06:52amaury.forgeotdarcsettitle: print is not thread safe -> bytearrays are not thread safe
2008-06-19 12:54:23giampaolo.rodolasetnosy: + giampaolo.rodola
2008-06-19 12:28:52amaury.forgeotdarcsetmessages: + msg68400
2008-06-19 11:03:51gpolosetnosy: + gpolo
messages: + msg68398
2008-06-19 10:06:53amaury.forgeotdarccreate