Skip to content

Commit 8ba0d7b

Browse files
gh-128942: make array module thread safe (#128943)
Co-authored-by: Kumar Aditya <[email protected]>
1 parent d027787 commit 8ba0d7b

File tree

4 files changed

+784
-159
lines changed

4 files changed

+784
-159
lines changed

Lib/test/test_array.py

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,21 @@
33
"""
44

55
import collections.abc
6+
import io
67
import unittest
78
from test import support
89
from test.support import import_helper
910
from test.support import os_helper
11+
from test.support import threading_helper
1012
from test.support import _2G
1113
import weakref
1214
import pickle
1315
import operator
16+
import random
1417
import struct
1518
import sys
19+
import sysconfig
20+
import threading
1621
import warnings
1722

1823
import array
@@ -1673,5 +1678,266 @@ def test_gh_128961(self):
16731678
self.assertRaises(StopIteration, next, it)
16741679

16751680

1681+
class FreeThreadingTest(unittest.TestCase):
1682+
# Test pretty much everything that can break under free-threading.
1683+
# Non-deterministic, but at least one of these things will fail if
1684+
# array module is not free-thread safe.
1685+
1686+
@unittest.skipUnless(support.Py_GIL_DISABLED, 'this test can only possibly fail with GIL disabled')
1687+
@threading_helper.reap_threads
1688+
@threading_helper.requires_working_threading()
1689+
def test_free_threading(self):
1690+
def pop1(b, a): # MODIFIES!
1691+
b.wait()
1692+
try: a.pop()
1693+
except IndexError: pass
1694+
1695+
def append1(b, a): # MODIFIES!
1696+
b.wait()
1697+
a.append(2)
1698+
1699+
def insert1(b, a): # MODIFIES!
1700+
b.wait()
1701+
a.insert(0, 2)
1702+
1703+
def extend(b, a): # MODIFIES!
1704+
c = array.array('i', [2])
1705+
b.wait()
1706+
a.extend(c)
1707+
1708+
def extend2(b, a, c): # MODIFIES!
1709+
b.wait()
1710+
a.extend(c)
1711+
1712+
def inplace_concat(b, a): # MODIFIES!
1713+
c = array.array('i', [2])
1714+
b.wait()
1715+
a += c
1716+
1717+
def inplace_concat2(b, a, c): # MODIFIES!
1718+
b.wait()
1719+
a += c
1720+
1721+
def inplace_repeat2(b, a): # MODIFIES!
1722+
b.wait()
1723+
a *= 2
1724+
1725+
def clear(b, a, *args): # MODIFIES!
1726+
b.wait()
1727+
a.clear()
1728+
1729+
def clear2(b, a, c): # MODIFIES c!
1730+
b.wait()
1731+
try: c.clear()
1732+
except BufferError: pass
1733+
1734+
def remove1(b, a): # MODIFIES!
1735+
b.wait()
1736+
try: a.remove(1)
1737+
except ValueError: pass
1738+
1739+
def fromunicode(b, a): # MODIFIES!
1740+
b.wait()
1741+
a.fromunicode('test')
1742+
1743+
def frombytes(b, a): # MODIFIES!
1744+
b.wait()
1745+
a.frombytes(b'0000')
1746+
1747+
def frombytes2(b, a, c): # MODIFIES!
1748+
b.wait()
1749+
a.frombytes(c)
1750+
1751+
def fromlist(b, a): # MODIFIES!
1752+
n = random.randint(0, 100)
1753+
b.wait()
1754+
a.fromlist([2] * n)
1755+
1756+
def ass_subscr2(b, a, c): # MODIFIES!
1757+
b.wait()
1758+
a[:] = c
1759+
1760+
def ass0(b, a): # modifies inplace
1761+
b.wait()
1762+
try: a[0] = 0
1763+
except IndexError: pass
1764+
1765+
def byteswap(b, a): # modifies inplace
1766+
b.wait()
1767+
a.byteswap()
1768+
1769+
def tounicode(b, a):
1770+
b.wait()
1771+
a.tounicode()
1772+
1773+
def tobytes(b, a):
1774+
b.wait()
1775+
a.tobytes()
1776+
1777+
def tolist(b, a):
1778+
b.wait()
1779+
a.tolist()
1780+
1781+
def tofile(b, a):
1782+
f = io.BytesIO()
1783+
b.wait()
1784+
a.tofile(f)
1785+
1786+
def reduce_ex2(b, a):
1787+
b.wait()
1788+
a.__reduce_ex__(2)
1789+
1790+
def reduce_ex3(b, a):
1791+
b.wait()
1792+
c = a.__reduce_ex__(3)
1793+
assert not c[1] or 0xdd not in c[1][3]
1794+
1795+
def copy(b, a):
1796+
b.wait()
1797+
c = a.__copy__()
1798+
assert not c or 0xdd not in c
1799+
1800+
def repr1(b, a):
1801+
b.wait()
1802+
repr(a)
1803+
1804+
def repeat2(b, a):
1805+
b.wait()
1806+
a * 2
1807+
1808+
def count1(b, a):
1809+
b.wait()
1810+
a.count(1)
1811+
1812+
def index1(b, a):
1813+
b.wait()
1814+
try: a.index(1)
1815+
except ValueError: pass
1816+
1817+
def contains1(b, a):
1818+
b.wait()
1819+
try: 1 in a
1820+
except ValueError: pass
1821+
1822+
def subscr0(b, a):
1823+
b.wait()
1824+
try: a[0]
1825+
except IndexError: pass
1826+
1827+
def concat(b, a):
1828+
b.wait()
1829+
a + a
1830+
1831+
def concat2(b, a, c):
1832+
b.wait()
1833+
a + c
1834+
1835+
def richcmplhs(b, a):
1836+
c = a[:]
1837+
b.wait()
1838+
a == c
1839+
1840+
def richcmprhs(b, a):
1841+
c = a[:]
1842+
b.wait()
1843+
c == a
1844+
1845+
def new(b, a):
1846+
tc = a.typecode
1847+
b.wait()
1848+
array.array(tc, a)
1849+
1850+
def repr_(b, a):
1851+
b.wait()
1852+
repr(a)
1853+
1854+
def irepeat(b, a): # MODIFIES!
1855+
b.wait()
1856+
a *= 2
1857+
1858+
def newi(b, l):
1859+
b.wait()
1860+
array.array('i', l)
1861+
1862+
def fromlistl(b, a, l): # MODIFIES!
1863+
b.wait()
1864+
a.fromlist(l)
1865+
1866+
def fromlistlclear(b, a, l): # MODIFIES LIST!
1867+
b.wait()
1868+
l.clear()
1869+
1870+
def iter_next(b, a, it): # MODIFIES ITERATOR!
1871+
b.wait()
1872+
list(it)
1873+
1874+
def iter_reduce(b, a, it):
1875+
b.wait()
1876+
c = it.__reduce__()
1877+
assert not c[1] or 0xdd not in c[1][0]
1878+
1879+
def check(funcs, a=None, *args):
1880+
if a is None:
1881+
a = array.array('i', [1])
1882+
1883+
barrier = threading.Barrier(len(funcs))
1884+
threads = []
1885+
1886+
for func in funcs:
1887+
thread = threading.Thread(target=func, args=(barrier, a, *args))
1888+
1889+
threads.append(thread)
1890+
1891+
with threading_helper.start_threads(threads):
1892+
pass
1893+
1894+
check([pop1] * 10)
1895+
check([pop1] + [subscr0] * 10)
1896+
check([append1] * 10)
1897+
check([insert1] * 10)
1898+
check([pop1] + [index1] * 10)
1899+
check([pop1] + [contains1] * 10)
1900+
check([insert1] + [repeat2] * 10)
1901+
check([pop1] + [repr1] * 10)
1902+
check([inplace_repeat2] * 10)
1903+
check([byteswap] * 10)
1904+
check([insert1] + [clear] * 10)
1905+
check([pop1] + [count1] * 10)
1906+
check([remove1] * 10)
1907+
check([clear] + [copy] * 10, array.array('B', b'0' * 0x400000))
1908+
check([pop1] + [reduce_ex2] * 10)
1909+
check([clear] + [reduce_ex3] * 10, array.array('B', b'0' * 0x400000))
1910+
check([pop1] + [tobytes] * 10)
1911+
check([pop1] + [tolist] * 10)
1912+
check([clear, tounicode] * 10, array.array('w', 'a'*10000))
1913+
check([clear, tofile] * 10, array.array('w', 'a'*10000))
1914+
check([clear] + [extend] * 10)
1915+
check([clear] + [inplace_concat] * 10)
1916+
check([clear] + [concat] * 10, array.array('w', 'a'*10000))
1917+
check([fromunicode] * 10, array.array('w', 'a'))
1918+
check([frombytes] * 10)
1919+
check([fromlist] * 10)
1920+
check([clear] + [richcmplhs] * 10, array.array('i', [1]*10000))
1921+
check([clear] + [richcmprhs] * 10, array.array('i', [1]*10000))
1922+
check([clear, ass0] * 10, array.array('i', [1]*10000)) # to test array_ass_item must disable Py_mp_ass_subscript
1923+
check([clear] + [new] * 10, array.array('w', 'a'*10000))
1924+
check([clear] + [repr_] * 10, array.array('B', b'0' * 0x40000))
1925+
check([clear] + [repr_] * 10, array.array('B', b'0' * 0x40000))
1926+
check([clear] + [irepeat] * 10, array.array('B', b'0' * 0x40000))
1927+
check([clear] + [iter_reduce] * 10, a := array.array('B', b'0' * 0x400), iter(a))
1928+
1929+
# make sure we handle non-self objects correctly
1930+
check([clear] + [newi] * 10, [2] * random.randint(0, 100))
1931+
check([fromlistlclear] + [fromlistl] * 10, array.array('i', [1]), [2] * random.randint(0, 100))
1932+
check([clear2] + [concat2] * 10, array.array('w', 'a'*10000), array.array('w', 'a'*10000))
1933+
check([clear2] + [inplace_concat2] * 10, array.array('w', 'a'*10000), array.array('w', 'a'*10000))
1934+
check([clear2] + [extend2] * 10, array.array('w', 'a'*10000), array.array('w', 'a'*10000))
1935+
check([clear2] + [ass_subscr2] * 10, array.array('w', 'a'*10000), array.array('w', 'a'*10000))
1936+
check([clear2] + [frombytes2] * 10, array.array('w', 'a'*10000), array.array('B', b'a'*10000))
1937+
1938+
# iterator stuff
1939+
check([clear] + [iter_next] * 10, a := array.array('i', [1] * 10), iter(a))
1940+
1941+
16761942
if __name__ == "__main__":
16771943
unittest.main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Make the :mod:`array` module safe under :term:`free threading`.

0 commit comments

Comments
 (0)