https://github.com/python/cpython/commit/a4f28a52b4b54c34100ee0891b9a15640ed3a7b2
commit: a4f28a52b4b54c34100ee0891b9a15640ed3a7b2
branch: main
author: John <[email protected]>
committer: vstinner <[email protected]>
date: 2026-10-01T21:39:34Z
summary:

gh-158316:  Avoid second buffer fetch on bytearray_decode() (#158485)

Co-authored-by: Victor Stinner <[email protected]>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-09-30-10-12-41.gh-issue-158316.Yv3kQe.rst
M Lib/test/test_bytes.py
M Objects/bytearrayobject.c

diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py
index b55863256cc37c..ea0a54dd5e1d35 100644
--- a/Lib/test/test_bytes.py
+++ b/Lib/test/test_bytes.py
@@ -18,6 +18,7 @@
 import textwrap
 import threading
 import unittest
+from _codecs import _unregister_error as _codecs_unregister_error
 
 import test.support
 from test import support
@@ -2195,6 +2196,30 @@ def delslice():
         self.assertRaises(BufferError, delslice)
         self.assertEqual(b, orig)
 
+    def test_decode_resize_forbidden(self):
+        # The storage is pinned while it is decoded, so an error handler
+        # cannot resize the bytearray.
+        b = bytearray(b'ab\xffcd')
+        errors = 'test.bytearray_decode_resize'
+        def handler(exc):
+            self.assertRaises(BufferError, b.clear)
+            self.assertRaises(BufferError, b.append, 0)
+            return ('?', exc.end)
+        self.addCleanup(_codecs_unregister_error, errors)
+        codecs.register_error(errors, handler)
+        for encoding in 'utf-8', 'utf-8-sig':
+            with self.subTest(encoding=encoding):
+                self.assertEqual(b.decode(encoding, errors), 'ab?cd')
+        self.assertEqual(b, b'ab\xffcd')
+
+    def test_decode_subclass_buffer(self):
+        # decode() decodes the buffer that the object exports.
+        class B(bytearray):
+            def __buffer__(self, flags):
+                return memoryview(b'other')
+        self.assertEqual(B(b'mine').decode(), 'other')
+        self.assertEqual(B(b'mine').decode('latin-1'), 'other')
+
     @test.support.cpython_only
     def test_obsolete_write_lock(self):
         _testcapi = import_helper.import_module('_testcapi')
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-30-10-12-41.gh-issue-158316.Yv3kQe.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-30-10-12-41.gh-issue-158316.Yv3kQe.rst
new file mode 100644
index 00000000000000..c95bab19af393a
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-30-10-12-41.gh-issue-158316.Yv3kQe.rst
@@ -0,0 +1,2 @@
+On Free Threading, speed up :meth:`bytearray.decode` by decoding the underlying
+storage directly instead of going through the buffer protocol.
diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c
index caf8bad88a280d..496d04a1704d46 100644
--- a/Objects/bytearrayobject.c
+++ b/Objects/bytearrayobject.c
@@ -2599,7 +2599,19 @@ bytearray_decode_impl(PyByteArrayObject *self, const 
char *encoding,
 {
     if (encoding == NULL)
         encoding = PyUnicode_GetDefaultEncoding();
-    return PyUnicode_FromEncodedObject((PyObject*)self, encoding, errors);
+    if (Py_TYPE(self)->tp_as_buffer->bf_getbuffer != bytearray_getbuffer) {
+        /* A subclass may export a different buffer. */
+        return PyUnicode_FromEncodedObject((PyObject*)self, encoding, errors);
+    }
+
+    /* Decode the storage directly instead of exporting a buffer, which
+       would re-acquire the critical section we already hold.  Increase
+       exports to prevent the storage from changing during the decode. */
+    self->ob_exports++;
+    PyObject *res = PyUnicode_Decode(PyByteArray_AS_STRING(self),
+                                     Py_SIZE(self), encoding, errors);
+    self->ob_exports--;
+    return res;
 }
 
 PyDoc_STRVAR(alloc_doc,

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to