Skip to content

Calling os.strerror() in parallel is not thread safe on FreeBSD #158893

Description

@vstinner

Recently, I made a large refactoring of locale encoding C functions (PR #158680): commit d19febb. I added an assertion to decode_current_locale() to check that the second _Py_mbstowcs() call returns the same size than the the first _Py_mbstowcs() call.

But this assertion failed on the "AMD64 FreeBSD16 3.x" buildbot while running test_concurrent_futures.test_interpreter_pool: https://buildbot.python.org/#/builders/1857/builds/874

test_blocking (test.test_concurrent_futures.test_interpreter_pool.InterpreterPoolExecutorTest.test_blocking) ... 

Assertion failed: (count == argsize), function decode_current_locale, file Python/fileutils.c, line 542.
Fatal Python error: Aborted

Current thread 0x00001cbd9c848810 [InterpreterPoolExec] (most recent call first):
  File "/home/buildbot/buildarea/3.x.opsec-fbsd16/build/Lib/genericpath.py", line 40 in isfile
  File "/home/buildbot/buildarea/3.x.opsec-fbsd16/build/Lib/site.py", line 983 in <genexpr>
  File "/home/buildbot/buildarea/3.x.opsec-fbsd16/build/Lib/site.py", line 977 in _venv
  File "/home/buildbot/buildarea/3.x.opsec-fbsd16/build/Lib/site.py", line 1108 in main
  File "/home/buildbot/buildarea/3.x.opsec-fbsd16/build/Lib/site.py", line 1132 in <module>
  File "<frozen importlib._bootstrap>", line 543 in _call_with_frames_removed
  File "<frozen importlib._bootstrap_external>", line 754 in exec_module
  File "<frozen importlib._bootstrap>", line 909 in _load_unlocked
  File "<frozen importlib._bootstrap>", line 1303 in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 1344 in _find_and_load

Current thread's C stack trace (most recent call first):
  <cannot get C stack on this system>

Extension modules: _testcapi (total: 1)

I wrote a stress test to try to reproduce the issue. I reproduced the issue on Linux (Fedora 44).

This stress test is special: it changes the LC_CTYPE locale every 10 ms. I don't think that it's a realistic scenario, but it might explain the assertion failure seen on FreeBSD 16.

import _testlimitedcapi
import locale
import threading
import time
import os

VERBOSE = True
NTHREAD = 50; LOOPS = 25
LONG_STRING = 1024 * 1024

# FreeBSD 16 locales
#CHANGE_LOCALES = ('C.UTF-8', "fr_FR.ISO8859-1", 'ja_JP.SJIS')
# Fedora 44 locales
CHANGE_LOCALES = ('C.utf8', "fr_FR.iso88591", 'ja_JP.ujis')

STRINGS = (
    b'abc',
    b'x' * LONG_STRING,
    b"blatin1:\xa7\xe9",
    b"b255:\xff",
    bytes(range(1, 256)),
)

unicode_decodelocale = _testlimitedcapi.unicode_decodelocale

def stress():
    for s in STRINGS:
        unicode_decodelocale(s, 'surrogateescape')

def worker(i):
    sched_yield = os.sched_yield
    for loop in range(LOOPS):
        if VERBOSE:
            os.write(1, f"worker {i} loop {loop}\n".encode())
        stress()
        sched_yield()
        #time.sleep(1e-3)

def change_locale(event):
    while True:
        for loc in CHANGE_LOCALES:
            if VERBOSE:
                os.write(1, f"set {loc} locale\n".encode())
            locale.setlocale(locale.LC_ALL, loc)
            time.sleep(0.010)
        if event.is_set():
            break

# Test locales
for loc in CHANGE_LOCALES:
    locale.setlocale(locale.LC_ALL, loc)

t1 = time.perf_counter()
event = threading.Event()
change_locale = threading.Thread(target=change_locale, args=(event,), name='change_locale')
change_locale.start()
threads = [threading.Thread(target=worker, args=(i,), name=f'worker{i}')
           for i in range(NTHREAD)]
for thread in threads:
    thread.start()
for thread in threads:
    thread.join()
event.set()
change_locale.join()
dt = time.perf_counter() - t1
print(f"{dt:.1f} seconds")

gdb traceback:

(gdb) 
#5  0x00000000007acb16 in decode_current_locale (arg=0x20002a05aa0 "blatin1:\247", <incomplete sequence \351>, 
    wstr=0x7fcd6cfefda0, wlen=0x7fcd6cfefd98, errors=_Py_ERROR_SURROGATEESCAPE) at Python/fileutils.c:542
542	            assert(count == argsize);
(gdb) p count
$1 = 9
(gdb) p argsize
$2 = 10
(gdb) p arg
$3 = 0x20002a05aa0 "blatin1:\247", <incomplete sequence \351>

Linked PRs

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions