Skip to content

Conversation

@jadonduff
Copy link
Contributor

@jadonduff jadonduff commented Aug 21, 2025

Fallback to ASCII in _thread.set_name() when pthread_setname_np() rejects UTF-8

Summary

Issue: test_set_name fails on OpenIndiana (and Solaris descendants).
It seems that pthread_setname_np() only accepts ASCII-only names there:

>>> import _thread
>>> _thread.set_name('€')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: [Errno 22] Invalid argument

Fix

1. _threadmodule.c

  • Attempt to encode the thread name in UTF-8 and call pthread_setname_np() as usual.
  • If the call fails with EINVAL, fall back to ASCII encoding using "replace" (non-ASCII → ?).
  • On macOS and other platforms that already accept UTF-8, the original UTF-8 succeeds and the fallback is never taken. ASCII names continue to work unchanged.
int rc;

// Fallback: If EINVAL, try ASCII encoding with "replace"
if (rc == EINVAL) {
    name_encoded = PyUnicode_AsEncodedString(name_obj, "ascii", "replace");
    if (name_encoded == NULL) {
        return NULL;
    }
#ifdef _PYTHREAD_NAME_MAXLEN
    if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) {
        PyObject *truncated;
        truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded),
                                              _PYTHREAD_NAME_MAXLEN);
        if (truncated == NULL) {
            Py_DECREF(name_encoded);
            return NULL;
        }
        Py_SETREF(name_encoded, truncated);
    }
#endif
    name = PyBytes_AS_STRING(name_encoded);
#ifdef __APPLE__
    rc = pthread_setname_np(name);
#elif defined(__NetBSD__)
    thread = pthread_self();
    rc = pthread_setname_np(thread, "%s", (void *)name);
#elif defined(HAVE_PTHREAD_SETNAME_NP)
    thread = pthread_self();
    rc = pthread_setname_np(thread, name);
#else /* defined(HAVE_PTHREAD_SET_NAME_NP) */
    thread = pthread_self();
    rc = 0;
    pthread_set_name_np(thread, name);
#endif
    Py_DECREF(name_encoded);
}

Explanation:

  • Keeps current behavior everywhere UTF-8 is supported.
  • Provides a fallback for platforms that only accept ASCII (e.g. OpenIndiana).
  • Avoids raising OSError(22) for non-ASCII names.

2. test_thread.py

  • Updated test_set_name to be lenient on platforms that reject non-ASCII names.
  • If OSError(22) occurs and the attempted name contained non-ASCII, the test is skipped instead of failing.
try:
    thread.start()
    thread.join()
    self.assertEqual(work_name, expected,
                     f"{len(work_name)=} and {len(expected)=}")
except OSError as exc:
    # Accept EINVAL (22) for non-ASCII names on platforms that do not support them
    if getattr(exc, 'errno', None) == 22 and any(ord(c) > 127 for c in name):
        self.skipTest(f"Platform does not support non-ASCII thread names: {exc}")
    else:
        raise

Explanation:

  • On macOS/Linux: runs as before, asserts the name round-trips correctly.
  • On OpenIndiana: skips the test if non-ASCII is not supported.

Verification

  • Ran locally on macOS (UTF-8 names continue to work as before).
  • Did not verify on OpenIndiana (cannot reproduce locally). Fix based on reports.
  • All tests passed on macOS ARM.

@python-cla-bot
Copy link

python-cla-bot bot commented Aug 21, 2025

All commit authors signed the Contributor License Agreement.

CLA signed

@bedevere-app
Copy link

bedevere-app bot commented Aug 21, 2025

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

Comment on lines 2632 to 2662
name_encoded = PyUnicode_AsEncodedString(name_obj, "ascii", "replace");
if (name_encoded == NULL) {
return NULL;
}
#ifdef _PYTHREAD_NAME_MAXLEN
if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) {
PyObject *truncated;
truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded),
_PYTHREAD_NAME_MAXLEN);
if (truncated == NULL) {
Py_DECREF(name_encoded);
return NULL;
}
Py_SETREF(name_encoded, truncated);
}
#endif
name = PyBytes_AS_STRING(name_encoded);
#ifdef __APPLE__
rc = pthread_setname_np(name);
#elif defined(__NetBSD__)
thread = pthread_self();
rc = pthread_setname_np(thread, "%s", (void *)name);
#elif defined(HAVE_PTHREAD_SETNAME_NP)
thread = pthread_self();
rc = pthread_setname_np(thread, name);
#else /* defined(HAVE_PTHREAD_SET_NAME_NP) */
thread = pthread_self();
rc = 0;
pthread_set_name_np(thread, name);
#endif
Py_DECREF(name_encoded);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not duplicate such complex code. Move it to function and reuse. Or use a loop (but this may be more complicated).

Copy link
Contributor Author

@jadonduff jadonduff Aug 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved to a function and reused. Despite most tests passing, I am receiving the following for the "Check if generated files are up to date" test:

 /usr/bin/ld: Modules/_threadmodule.o: in function `_thread_set_name_impl':
/home/runner/work/cpython/cpython/./Modules/_threadmodule.c:2617:(.text+0x2ed5): undefined reference to `_set_thread_name'
/usr/bin/ld: /home/runner/work/cpython/cpython/./Modules/_threadmodule.c:2637:(.text+0x2f99): undefined reference to `_set_thread_name'
collect2: error: ld returned 1 exit status
make: *** [Makefile:1927: Programs/_freeze_module] Error 1
Error: Process completed with exit code 2.

Please let me know if you see the issue. It appeared to be working before changing to function.

thread.join()
self.assertEqual(work_name, expected,
f"{len(work_name)=} and {len(expected)=}")
except OSError as exc:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is OSError even raised here? The test failure was different -- that work_name was unexpectedly empty.

Copy link
Contributor Author

@jadonduff jadonduff Aug 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added:

                    if any(ord(c) > 127 for c in name) and (not work_name or work_name == ""):
                        self.skipTest(f"Platform does not support non-ASCII thread names: got empty name for {name!r}")
                    self.assertEqual(work_name, expected,
                                     f"{len(work_name)=} and {len(expected)=}")

Kept OSError fallback because it was shown in original issue, but can remove if needed.

>>> import _thread
>>> _thread.set_name('€')
Traceback (most recent call last):
  File "<python-input-1>", line 1, in <module>
    _thread.set_name('€')
    ~~~~~~~~~~~~~~~~^^^^^
OSError: [Errno 22] Invalid argument

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was shown that _thread.set_name() fails, but that OSError is not leaked from the Thread code.

@bedevere-app
Copy link

bedevere-app bot commented Aug 21, 2025

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

@jadonduff
Copy link
Contributor Author

Please see comments regarding the requested changes. OS tests passed, but a function not found error occurred in "Tests / Check if generated files are up to date (pull_request)", which I am having trouble fixing. Apologies - this is my first attempt at contributing to Python.

@jadonduff
Copy link
Contributor Author

I have made the requested changes; please review again

@bedevere-app
Copy link

bedevere-app bot commented Aug 22, 2025

Thanks for making the requested changes!

@serhiy-storchaka: please review the changes made to this pull request.

Comment on lines 2651 to 2660
#ifdef _PYTHREAD_NAME_MAXLEN
if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) {
PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), _PYTHREAD_NAME_MAXLEN);
if (truncated == NULL) {
Py_DECREF(name_encoded);
return NULL;
}
Py_SETREF(name_encoded, truncated);
}
#endif
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, please try to avoid duplicating code. This should be factored out into its own function. Here's an outline:

static PyObject *
get_truncated(PyObject *name_encoded /* stolen */)
{
#ifdef _PYTHREAD_NAME_MAXLEN
    if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) {
        PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), _PYTHREAD_NAME_MAXLEN);
        if (truncated == NULL) {
            Py_DECREF(name_encoded);
            return NULL;
        }
        Py_SETREF(name_encoded, truncated);
    }
#endif
    return name_encoded;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or simply include the encoding and truncating code in _set_thread_name(). BTW, there is no need to use underscored names here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created function encode_thread_name and used to replace duplicated code


with warnings.catch_warnings(record=True) as warnings_log:
CustomRLock(1, b=2)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stray newline change:

Suggested change

@serhiy-storchaka serhiy-storchaka self-requested a review September 2, 2025 07:41
@jadonduff
Copy link
Contributor Author

Thank you. I have made the requested changes; please review again.

@bedevere-app
Copy link

bedevere-app bot commented Sep 2, 2025

Thanks for making the requested changes!

@serhiy-storchaka: please review the changes made to this pull request.

Copy link
Member

@vstinner vstinner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Member

@serhiy-storchaka serhiy-storchaka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. 👍

@serhiy-storchaka serhiy-storchaka enabled auto-merge (squash) September 2, 2025 14:59
@serhiy-storchaka serhiy-storchaka merged commit c19db1d into python:main Sep 2, 2025
45 checks passed
@serhiy-storchaka serhiy-storchaka added the needs backport to 3.14 bugs and security fixes label Sep 2, 2025
@miss-islington-app
Copy link

Thanks @jadonduff for the PR, and @serhiy-storchaka for merging it 🌮🎉.. I'm working now to backport this PR to: 3.14.
🐍🍒⛏🤖

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Sep 2, 2025
…38017)

Encode Solaris/Illumos thread names to ASCII, since
OpenIndiana does not support non-ASCII names.

Add tests for setting non-ASCII name for the main thread.
(cherry picked from commit c19db1d)

Co-authored-by: jadonduff <[email protected]>
Co-authored-by: Serhiy Storchaka <[email protected]>
@bedevere-app
Copy link

bedevere-app bot commented Sep 2, 2025

GH-138384 is a backport of this pull request to the 3.14 branch.

@bedevere-app bedevere-app bot removed the needs backport to 3.14 bugs and security fixes label Sep 2, 2025
@vstinner
Copy link
Member

vstinner commented Sep 2, 2025

Thanks @jadonduff for your fix.

lkollar pushed a commit to lkollar/cpython that referenced this pull request Sep 9, 2025
…38017)

Encode Solaris/Illumos thread names to ASCII, since
OpenIndiana does not support non-ASCII names.

Add tests for setting non-ASCII name for the main thread.

Co-authored-by: Serhiy Storchaka <[email protected]>
vstinner pushed a commit that referenced this pull request Oct 7, 2025
#138384)

gh-138004: Fix setting a thread name on OpenIndiana (GH-138017)

Encode Solaris/Illumos thread names to ASCII, since
OpenIndiana does not support non-ASCII names.

Add tests for setting non-ASCII name for the main thread.
(cherry picked from commit c19db1d)

Co-authored-by: jadonduff <[email protected]>
Co-authored-by: Serhiy Storchaka <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants