Skip to content

Crash on concurrent first read of Element.text or Element.tail on the free-threaded build #161062

Description

@gpshead

Crash report

What happened?

On the free-threaded build, two threads doing the first read of Element.text or Element.tail of an element built by the C parser can crash. Text that arrives from expat in several chunks (anything containing an entity such as &, or the whitespace tail in an indented document) is kept as a list and joined on first access; element_get_text() and element_get_tail() in Modules/_elementtree.c do that join without a lock, so both threads join the list and both release the element's only reference to it.

import threading
import xml.etree.ElementTree as ET

doc = "<root>" + "<item>a &amp; b</item>" * 500 + "</root>"
for _ in range(200):
    root = ET.fromstring(doc)
    barrier = threading.Barrier(8)
    def read():
        barrier.wait()
        for item in root:
            item.text
    threads = [threading.Thread(target=read) for _ in range(8)]
    for t in threads:
        t.start()
    for t in threads:
        t.join()

A debug build aborts with Modules/_elementtree.c:587: _Py_NegativeRefcount: Assertion failed: object has negative ref count or segfaults; concurrent ET.tostring(root) calls hit it the same way. The default build is not affected.

This is a small part of gh-146022. gh-149816 item (87) is about the getter racing the text setter, which is a different race in the same functions.

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Linked PRs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions