Skip to content

Python array module is not free-thread safe. #128942

Description

@tom-pytel

Bug report

Bug description:

The python array module is not currently free-thread safe. The error generator below can generate segfaults of aborts for most of the array object methods. I found #116738 which lists this module as in need of an audit, an apparenly closed start on this in #120103 and no owner for this module in the Experts Index, so decided to give it a go (see attached PR).

Reproducer, any single check(...) call below will generate some kind of segfault or abort fairly quickly.

fromarrayimportarrayfromioimportBytesIOfromrandomimportrandintimportthreadingdefpop1(b, a): # MODIFIES!b.wait()
try: a.pop()
exceptIndexError: passdefappend1(b, a): # MODIFIES!b.wait()
a.append(2)
definsert1(b, a): # MODIFIES!b.wait()
a.insert(0, 2)
defextend(b, a): # MODIFIES!c=array('i', [2])
b.wait()
a.extend(c)
defextend2(b, a, c): # MODIFIES!b.wait()
a.extend(c)
definplace_concat(b, a): # MODIFIES!c=array('i', [2])
b.wait()
a+=cdefinplace_concat2(b, a, c): # MODIFIES!b.wait()
a+=cdefinplace_repeat2(b, a): # MODIFIES!b.wait()
a*=2defclear(b, a, *args): # MODIFIES!b.wait()
a.clear()
defclear2(b, a, c): # MODIFIES c!b.wait()
try: c.clear()
exceptBufferError: passdefremove1(b, a): # MODIFIES!b.wait()
try: a.remove(1)
exceptValueError: passdeffromunicode(b, a): # MODIFIES!b.wait()
a.fromunicode('test')
deffrombytes(b, a): # MODIFIES!b.wait()
a.frombytes(b'0000')
deffrombytes2(b, a, c): # MODIFIES!b.wait()
a.frombytes(c)
deffromlist(b, a): # MODIFIES!n=randint(0, 100)
b.wait()
a.fromlist([2] *n)
defass_subscr2(b, a, c): # MODIFIES!b.wait()
a[:] =cdefass0(b, a): # modifies inplaceb.wait()
try: a[0] =0exceptIndexError: passdefbyteswap(b, a): # modifies inplaceb.wait()
a.byteswap()
deftounicode(b, a):
b.wait()
a.tounicode()
deftobytes(b, a):
b.wait()
a.tobytes()
deftolist(b, a):
b.wait()
a.tolist()
deftofile(b, a):
f=BytesIO()
b.wait()
a.tofile(f)
defreduce_ex2(b, a):
b.wait()
a.__reduce_ex__(2)
defreduce_ex3(b, a):
b.wait()
c=a.__reduce_ex__(3)
assertnotc[1] orb'\xdd'notinc[1][3]
defcopy(b, a):
b.wait()
c=a.__copy__()
assertnotcor0xddnotincdefrepr1(b, a):
b.wait()
repr(a)
defrepeat2(b, a):
b.wait()
a*2defcount1(b, a):
b.wait()
a.count(1)
defindex1(b, a):
b.wait()
try: a.index(1)
exceptValueError: passdefcontains1(b, a):
b.wait()
try: 1inaexceptValueError: passdefsubscr0(b, a):
b.wait()
try: a[0]
exceptIndexError: passdefconcat(b, a):
b.wait()
a+adefconcat2(b, a, c):
b.wait()
a+cdefrichcmplhs(b, a):
c=a[:]
b.wait()
a==cdefrichcmprhs(b, a):
c=a[:]
b.wait()
c==adefnew(b, a):
tc=a.typecodeb.wait()
array(tc, a)
defrepr_(b, a):
b.wait()
repr(a)
defirepeat(b, a): # MODIFIES!b.wait()
a*=2defnewi(b, l):
b.wait()
array('i', l)
deffromlistl(b, a, l): # MODIFIES!b.wait()
a.fromlist(l)
deffromlistlclear(b, a, l): # MODIFIES LIST!b.wait()
l.clear()
defiter_next(b, a, it): # MODIFIES ITERATOR!b.wait()
list(it)
defiter_reduce(b, a, it):
b.wait()
c=it.__reduce__()
assertnotc[1] orb'\xdd'notinc[1][0]
defcheck(funcs, a=None, *args):
ifaisNone:
a=array('i', [1])
barrier=threading.Barrier(len(funcs))
thrds= []
forfuncinfuncs:
thrd=threading.Thread(target=func, args=(barrier, a, *args))
thrds.append(thrd)
thrd.start()
forthrdinthrds:
thrd.join()
if__name__=="__main__":
whileTrue:
check([pop1] *10)
check([pop1] + [subscr0] *10)
check([append1] *10)
check([insert1] *10)
check([pop1] + [index1] *10)
check([pop1] + [contains1] *10)
check([insert1] + [repeat2] *10)
check([pop1] + [repr1] *10)
check([inplace_repeat2] *10)
check([byteswap] *10)
check([insert1] + [clear] *10)
check([pop1] + [count1] *10)
check([remove1] *10)
check([clear] + [copy] *10, array('B', b'0'*0x400000))
check([pop1] + [reduce_ex2] *10)
check([clear] + [reduce_ex3] *10, array('B', b'0'*0x400000))
check([pop1] + [tobytes] *10)
check([pop1] + [tolist] *10)
check([clear, tounicode] *10, array('w', 'a'*10000))
check([clear, tofile] *10, array('w', 'a'*10000))
check([clear] + [extend] *10)
check([clear] + [inplace_concat] *10)
check([clear] + [concat] *10, array('w', 'a'*10000))
check([fromunicode] *10, array('w', 'a'))
check([frombytes] *10)
check([fromlist] *10)
check([clear] + [richcmplhs] *10, array('i', [1]*10000))
check([clear] + [richcmprhs] *10, array('i', [1]*10000))
check([clear, ass0] *10, array('i', [1]*10000)) # to test array_ass_item must disable Py_mp_ass_subscriptcheck([clear] + [new] *10, array('w', 'a'*10000))
check([clear] + [repr_] *10, array('B', b'0'*0x40000))
check([clear] + [repr_] *10, array('B', b'0'*0x40000))
check([clear] + [irepeat] *10, array('B', b'0'*0x40000))
check([clear] + [iter_reduce] *10, a:=array('B', b'0'*0x400), iter(a))
# make sure we handle non-self objects correctlycheck([clear] + [newi] *10, [2] *randint(0, 100))
check([fromlistlclear] + [fromlistl] *10, array('i', [1]), [2] *randint(0, 100))
check([clear2] + [concat2] *10, array('w', 'a'*10000), array('w', 'a'*10000))
check([clear2] + [inplace_concat2] *10, array('w', 'a'*10000), array('w', 'a'*10000))
check([clear2] + [extend2] *10, array('w', 'a'*10000), array('w', 'a'*10000))
check([clear2] + [ass_subscr2] *10, array('w', 'a'*10000), array('w', 'a'*10000))
check([clear2] + [frombytes2] *10, array('w', 'a'*10000), array('B', b'a'*10000))
# iterator stuffcheck([clear] + [iter_next] *10, a:=array('i', [1] *10), iter(a))

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions