Skip to content

Commit 9348e13

Browse files
junnplusblurb-it[bot]serhiy-storchaka
authored
gh-88336: Fix UnicodeDecodeError with multibyte utf8 characters in ShareableList (GH-26328)
* bpo-44170: Fix UnicodeDecodeError with multibyte utf8 characters in ShareableList * Round up the allocated size instead of rejecting the exact fit Allocating one extra alignment block for a value whose encoded size is a multiple of the alignment made a copy of the list wider than the original. Round the size up instead, so that it is the same for the same value, and keep allowing a value which fills the slot exactly: rejecting it broke setting an 8-byte string in a slot of an integer. --------- Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
1 parent b71fadc commit 9348e13

3 files changed

Lines changed: 23 additions & 15 deletions

File tree

‎Lib/multiprocessing/shared_memory.py‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -305,14 +305,21 @@ def _extract_recreation_code(value):
305305
else:
306306
return 3 # NoneType
307307

308+
@staticmethod
309+
def _encode_value(value):
310+
if not isinstance(value, str):
311+
return value
312+
else:
313+
return value.encode(_encoding)
314+
308315
def __init__(self, sequence=None, *, name=None):
309316
if name is None or sequence is not None:
310317
sequence = sequence or ()
311318
_formats = [
312319
self._types_mapping[type(item)]
313320
if not isinstance(item, (str, bytes))
314321
else self._types_mapping[type(item)] % (
315-
self._alignment * (len(item) // self._alignment + 1),
322+
self._alignment * ((len(self._encode_value(item)) - 1) // self._alignment + 1),
316323
)
317324
for item in sequence
318325
]
@@ -353,7 +360,7 @@ def __init__(self, sequence=None, *, name=None):
353360
"".join(_formats),
354361
self.shm.buf,
355362
self._offset_data_start,
356-
*(v.encode(_enc) if isinstance(v, str) else v for v in sequence)
363+
*(self._encode_value(v) for v in sequence)
357364
)
358365
struct.pack_into(
359366
self._format_packing_metainfo,
@@ -463,8 +470,7 @@ def __setitem__(self, position, value):
463470
else:
464471
allocated_length = self._allocated_offsets[position + 1] - item_offset
465472

466-
encoded_value = (value.encode(_encoding)
467-
if isinstance(value, str) else value)
473+
encoded_value = self._encode_value(value)
468474
if len(encoded_value) > allocated_length:
469475
raise ValueError("bytes/str item exceeds available storage")
470476
if current_format[-1] == "s":

‎Lib/test/_test_multiprocessing.py‎

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5106,7 +5106,7 @@ def test_shared_memory_SharedMemoryManager_basics(self):
51065106

51075107
def test_shared_memory_ShareableList_basics(self):
51085108
sl = shared_memory.ShareableList(
5109-
['howdy', b'HoWdY', -273.154, 100, None, True, 42]
5109+
['howdy', b'HoWdY', -273.154, 100, None, True, 42, '💥 💥']
51105110
)
51115111
self.addCleanup(sl.shm.unlink)
51125112

@@ -5116,22 +5116,22 @@ def test_shared_memory_ShareableList_basics(self):
51165116

51175117
# Index Out of Range (get)
51185118
with self.assertRaises(IndexError):
5119-
sl[7]
5119+
sl[8]
51205120

51215121
# Index Out of Range (set)
51225122
with self.assertRaises(IndexError):
5123-
sl[7] = 2
5123+
sl[8] = 2
51245124

51255125
# Assign value without format change (str -> str)
51265126
current_format = sl._get_packing_format(0)
51275127
sl[0] = 'howdy'
51285128
self.assertEqual(current_format, sl._get_packing_format(0))
51295129

51305130
# Verify attributes are readable.
5131-
self.assertEqual(sl.format, '8s8sdqxxxxxx?xxxxxxxx?q')
5131+
self.assertEqual(sl.format, '8s8sdqxxxxxx?xxxxxxxx?q16s')
51325132

51335133
# Exercise len().
5134-
self.assertEqual(len(sl), 7)
5134+
self.assertEqual(len(sl), 8)
51355135

51365136
# Exercise index().
51375137
with warnings.catch_warnings():
@@ -5143,30 +5143,31 @@ def test_shared_memory_ShareableList_basics(self):
51435143

51445144
# Exercise retrieving individual values.
51455145
self.assertEqual(sl[0], 'howdy')
5146-
self.assertEqual(sl[-2], True)
5146+
self.assertEqual(sl[-3], True)
5147+
self.assertEqual(sl[-1], '💥 💥')
51475148

51485149
# Exercise iterability.
51495150
self.assertEqual(
51505151
tuple(sl),
5151-
('howdy', b'HoWdY', -273.154, 100, None, True, 42)
5152+
('howdy', b'HoWdY', -273.154, 100, None, True, 42, '💥 💥')
51525153
)
51535154

51545155
# Exercise modifying individual values.
51555156
sl[3] = 42
51565157
self.assertEqual(sl[3], 42)
51575158
sl[4] = 'some' # Change type at a given position.
51585159
self.assertEqual(sl[4], 'some')
5159-
self.assertEqual(sl.format, '8s8sdq8sxxxxxxx?q')
5160+
self.assertEqual(sl.format, '8s8sdq8sxxxxxxx?q16s')
51605161
with self.assertRaisesRegex(ValueError,
51615162
"exceeds available storage"):
51625163
sl[4] = 'far too many'
51635164
self.assertEqual(sl[4], 'some')
5164-
sl[0] = 'encodés' # Exactly 8 bytes of UTF-8 data
5165-
self.assertEqual(sl[0], 'encodés')
5165+
sl[0] = 'éncodé' # Exactly 8 bytes of UTF-8 data
5166+
self.assertEqual(sl[0], 'éncodé')
51665167
self.assertEqual(sl[1], b'HoWdY') # no spillage
51675168
with self.assertRaisesRegex(ValueError,
51685169
"exceeds available storage"):
5169-
sl[0] = 'encodées' # Exactly 9 bytes of UTF-8 data
5170+
sl[0] = 'éncodés' # Exactly 9 bytes of UTF-8 data
51705171
self.assertEqual(sl[1], b'HoWdY')
51715172
with self.assertRaisesRegex(ValueError,
51725173
"exceeds available storage"):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix UnicodeDecodeError with multibyte utf8 characters in ShareableList.

0 commit comments

Comments
 (0)